import logging
from typing import List, Optional
from uuid import UUID

from django.conf import settings
from django.template import loader

from staff.lib.db import atomic
from staff.lib.startrek.issues import get_issue_queue

from staff.budget_position.workflow_service import entities
from staff.budget_position.workflow_service.use_cases.interfaces import (
    CreditManagementServiceInterface,
    StartrekService,
)


logger = logging.getLogger(__name__)


class PushWorkflow:
    WORKFLOW_RESULT_OK: str = 'wf_ok'
    WORKFLOW_RESULT_ERROR: str = 'wf_err'

    def __init__(
        self,
        repository: entities.WorkflowRepositoryInterface,
        startrek_service: StartrekService,
        staff_service: entities.StaffService,
        femida_service: entities.FemidaService,
        credit_management_service: CreditManagementServiceInterface,
    ):
        self._repository = repository
        self._startrek_service = startrek_service
        self._staff_service = staff_service
        self._femida_service = femida_service
        self._credit_management_service = credit_management_service

    def get_workflows(self, status: str) -> List[UUID]:
        return self._repository.get_workflows(status)

    @atomic
    def try_send_notification_for_workflow(self, workflow_id: UUID) -> None:
        if not self._repository.can_workflow_be_finalized(workflow_id):
            return

        workflow = self._repository.get_by_id(workflow_id)

        if workflow.has_errors():
            workflow_result = self.WORKFLOW_RESULT_ERROR
            text = self._workflow_push_failed_text(workflow)
        else:
            workflow_result = self.WORKFLOW_RESULT_OK
            text = self._workflow_pushed_text(workflow)

        for key in self._repository.get_related_tickets(workflow_id):
            st_local_fields_update = self._get_local_fields(key, workflow_result)
            self._startrek_service.add_comment(key=key, text=text)
            if st_local_fields_update:
                self._startrek_service.update_issue(key, **st_local_fields_update)

        workflow.mark_finished()
        self._notify_credit_management_service(workflow)
        self._repository.save(workflow)

    def _notify_credit_management_service(self, workflow: entities.AbstractWorkflow):
        if workflow.credit_management_id:
            self._credit_management_service.notify_about_changes_for_application(workflow.credit_management_id, None)

    def _workflow_push_failed_text(self, workflow: entities.AbstractWorkflow):
        return loader.render_to_string(
                template_name='startrek/workflow_push_failed.html',
                context={
                    'staff_host': settings.STAFF_HOST,
                    'workflow_id': workflow.id,
                    'changes': workflow.changes,
                    'error': 'Не удалось провести изменения в ОЕБС',
                },
            )

    def _workflow_pushed_text(self, workflow: entities.AbstractWorkflow) -> str:
        return loader.render_to_string(
            template_name='startrek/workflow_pushed.html',
            context={
                'staff_host': settings.STAFF_HOST,
                'workflow_id': workflow.id,
                'changes': workflow.changes,
            },
        )

    def _responsible_hr_analyst(self, department_ids: List[int], workflow_id: UUID) -> entities.Person:
        if not department_ids:
            logger.warning('Cannot find related departments in workflow %s', workflow_id)

        responsible_persons = {
            self._staff_service.hr_analyst_responsible_for_department(department_id)
            for department_id in department_ids
        }
        responsible_persons.discard(None)
        result: entities.Person
        if responsible_persons:
            result = next(iter(responsible_persons))
        else:
            logger.warning('No responsible person found in workflow %s', workflow_id)
            result = self._staff_service.default_hr_analyst()

        return result

    def push_workflow(self, workflow_id: UUID):
        from staff.budget_position.workflow_service import OEBSError, FemidaError, WorkflowRegistryService

        exception: Optional[Exception] = None

        with atomic():
            workflow = self._repository.get_by_id(workflow_id)
            logger.info('Trying to push changes for workflow %s', workflow_id)

            if workflow.should_close_vacancy:
                try:
                    self._femida_service.close_vacancy(workflow.id)
                except FemidaError as exc:
                    logger.info('Pushing workflow %s aborted due to FemidaError', workflow_id)
                    exception = exc

            if exception is None:
                department_ids = self._repository.get_related_department_ids(workflow.id)
                if not department_ids:
                    logger.warning('Cannot find related departments in workflow %s', workflow_id)
                hr_analyst = self._responsible_hr_analyst(department_ids, workflow.id)
                try:
                    WorkflowRegistryService().push_workflow_to_oebs(workflow.id, hr_analyst.id)
                except OEBSError as exc:
                    exception = exc

        if exception is not None:
            with atomic():
                self._mark_workflow_failed(exception, workflow_id, workflow.changes)

        logger.info('Pushed workflow %s', workflow_id)

    def _mark_workflow_failed(self, exc: Exception, workflow_id: UUID, changes: List[entities.Change]) -> None:
        self._repository.mark_changes_as_failed(workflow_id, exc)

        for key in self._repository.get_related_tickets(workflow_id):
            st_local_fields_update = self._get_local_fields(key, self.WORKFLOW_RESULT_ERROR)
            self._startrek_service.add_comment(
                key=key,
                text=loader.render_to_string(
                    template_name='startrek/workflow_push_failed.html',
                    context={
                        'staff_host': settings.STAFF_HOST,
                        'workflow_id': workflow_id,
                        'changes': changes,
                        'error': exc,
                    },
                ),
            )
            if st_local_fields_update:
                self._startrek_service.update_issue(key, **st_local_fields_update)

    @staticmethod
    def _get_local_fields(issue_key, workflow_result):
        queue_uid = settings.STARTREK_QUEUE_UIDS.get(get_issue_queue(issue_key))

        if queue_uid:
            workflow_status_field = f'{queue_uid}--{settings.STARTREK_WORKFLOW_STATUS_FIELD}'
            return {workflow_status_field: workflow_result}

        return {}
