import logging
from typing import Dict, List, Callable, Any

from django.conf import settings

from staff.lib.models.departments_chain import get_departments_tree  # TODO: move to staff service
from staff.lib.requests import Session

from staff.budget_position.workflow_service.entities import (
    BonusSchemeId,
    BonusSchemeIdByGroupRequest,
    BonusSchemeIdRequest,
    ReviewSchemeId,
    ReviewSchemeIdByGroupRequest,
    ReviewSchemeIdRequest,
    RewardCategory,
    RewardCategoryRequest,
    RewardSchemeId,
    RewardSchemeIdRequest,
    TableflowService as ITableflowService,
)


logger = logging.getLogger(__name__)


class TableflowService(ITableflowService):
    def __init__(self):
        self._session = Session()
        self._session.headers.update({
            'Authorization': 'OAuth %s' % settings.ROBOT_STAFF_OAUTH_TOKEN,
        })

    def review_scheme_id(self, requests: List[ReviewSchemeIdRequest]) -> List[ReviewSchemeId or None]:
        if not requests:
            return []

        for request in requests:
            assert request.occupation_id is not None
            assert request.department_id is not None

        url = f'https://{settings.TABLE_FLOW_HOST}/rules/staff_review_table/execute_bulk/'
        response_to_request_idx_map, table_flow_requests = self._prepare_requests(
            requests,
            lambda request: [
                {
                    'department_url': department_url,
                    'occupation_code': request.occupation_id,
                    'grade_level': request.grade_level or -1,
                }
                for department_url in self._ancestors_urls(request.department_id)
            ]
        )

        response = self._post(url, {'requests': table_flow_requests})
        return self._parse_response(response, 'review_id', response_to_request_idx_map, len(requests))

    def review_scheme_id_by_group(self, requests: List[ReviewSchemeIdByGroupRequest]) -> List[ReviewSchemeId or None]:
        if not requests:
            return []

        for request in requests:
            assert request.department_id is not None
            assert request.occupation_review_group is not None

        url = f'https://{settings.TABLE_FLOW_HOST}/rules/staff_review_group_table/execute_bulk/'
        response_to_request_idx_map, table_flow_requests = self._prepare_requests(
            requests,
            lambda request: [
                {
                    'department_url': department_url,
                    'review_group': request.occupation_review_group,
                    'grade_level': request.grade_level or -1,
                }
                for department_url in self._ancestors_urls(request.department_id)
            ]
        )

        response = self._post(url, {'requests': table_flow_requests})
        return self._parse_response(response, 'review_id', response_to_request_idx_map, len(requests))

    def bonus_scheme_id(self, requests: List[BonusSchemeIdRequest]) -> List[BonusSchemeId or None]:
        if not requests:
            return []

        for request in requests:
            assert request.occupation_id is not None
            assert request.department_id is not None

        url = f'https://{settings.TABLE_FLOW_HOST}/rules/staff_bonus_table/execute_bulk/'
        response_to_request_idx_map, table_flow_requests = self._prepare_requests(
            requests,
            lambda request: [
                {
                    'department_url': department_url,
                    'occupation_code': request.occupation_id,
                    'grade_level': request.grade_level or -1,
                }
                for department_url in self._ancestors_urls(request.department_id)
            ]
        )

        response = self._post(url, {'requests': table_flow_requests})
        return self._parse_response(response, 'bonus_id', response_to_request_idx_map, len(requests))

    def bonus_scheme_id_by_group(self, requests: List[BonusSchemeIdByGroupRequest]) -> List[BonusSchemeId or None]:
        if not requests:
            return []

        for request in requests:
            assert request.department_id is not None
            assert request.occupation_bonus_group is not None

        url = f'https://{settings.TABLE_FLOW_HOST}/rules/staff_bonus_group_table/execute_bulk/'
        response_to_request_idx_map, table_flow_requests = self._prepare_requests(
            requests,
            lambda request: [
                {
                    'department_url': department_url,
                    'bonus_group': request.occupation_bonus_group,
                    'grade_level': request.grade_level or -1,
                }
                for department_url in self._ancestors_urls(request.department_id)
            ]
        )

        response = self._post(url, {'requests': table_flow_requests})
        return self._parse_response(response, 'bonus_id', response_to_request_idx_map, len(requests))

    def reward_category(self, requests: List[RewardCategoryRequest]) -> List[RewardCategory or None]:
        if not requests:
            return []

        for request in requests:
            assert request.occupation_reward_group is not None

        url = f'https://{settings.TABLE_FLOW_HOST}/rules/staff_reward_category_table/execute_bulk/'
        response_to_request_idx_map, table_flow_requests = self._prepare_requests(
            requests,
            lambda request: [
                {'grade_level': request.grade_level or -1, 'occupation_reward_group': request.occupation_reward_group}
            ]
        )

        response = self._post(url, {'requests': table_flow_requests})
        return self._parse_response(response, 'category', response_to_request_idx_map, len(requests))

    def reward_scheme_id(self, requests: List[RewardSchemeIdRequest]) -> List[RewardSchemeId or None]:
        if not requests:
            return []

        for request in requests:
            assert request.department_id is not None
            assert request.category is not None

        url = f'https://{settings.TABLE_FLOW_HOST}/rules/staff_reward_table/execute_bulk/'
        response_to_request_idx_map, table_flow_requests = self._prepare_requests(
            requests,
            lambda request: [
                {
                    'department_url': department_url,
                    'category': request.category,
                    'is_internship': int(request.is_internship),
                }
                for department_url in self._ancestors_urls(request.department_id)
            ]
        )

        response = self._post(url, {'requests': table_flow_requests})
        return self._parse_response(response, 'reward_scheme_id', response_to_request_idx_map, len(requests))

    def _prepare_requests(self, requests: List[Any], requests_factory: Callable[[Any], List[Any]]):
        # нужен чтобы смапать индекс элемента ответа на изначальный запрос, так как
        # каждый попадающий в эту функцию запрос порождает множество запросов в table flow
        response_to_request_idx_map = []
        results = []
        for original_idx, request in enumerate(requests):
            for created_request in requests_factory(request):
                response_to_request_idx_map.append(original_idx)
                results.append(created_request)

        return response_to_request_idx_map, results

    def _parse_response(
        self,
        response: Dict,
        field: str,
        response_to_request_idx_map: List[int],
        requests_count: int,
    ) -> List[Any]:
        result = [None] * requests_count
        if not response:
            logger.error('Got empty response from tableflow')
            return result

        if not response.get('results'):
            logger.error('Got empty response from tableflow')
            return result

        last_found_rule_priority_for_request = {}
        for idx, table_flow_result in enumerate(response['results']):
            if table_flow_result:
                request_idx = response_to_request_idx_map[idx]
                if 'priority' not in table_flow_result:
                    result[request_idx] = table_flow_result[field]
                else:
                    new_priority = int(table_flow_result['priority'])
                    previous_rule_priority = last_found_rule_priority_for_request.get(request_idx, -1)
                    if previous_rule_priority <= new_priority:
                        result[request_idx] = table_flow_result[field]
                        last_found_rule_priority_for_request[request_idx] = new_priority

        return result

    def _ancestors_urls(self, department_id: int):
        dep_tree = get_departments_tree([department_id], ('url',))

        if not dep_tree or department_id not in dep_tree:
            raise ValueError(f'Department with {department_id} not found')

        return [item['url'] for item in dep_tree[department_id]]

    def _get(self, url, params):
        response = self._session.get(url, params=params, timeout=(2, 5, 10))
        response.raise_for_status()
        return response.json()

    def _post(self, url, request: Dict):
        response = self._session.post(url, json=request, timeout=(2, 5, 10))
        response.raise_for_status()
        return response.json()
