from typing import Dict, Any

from django_yauth.authentication_mechanisms.tvm import TvmServiceRequest

from django.conf import settings
from django.db.models import Q
from django.http import HttpResponseForbidden, JsonResponse
from django.views.decorators.http import require_GET

from staff.departments.models import DepartmentRoles, Geography
from staff.lib.decorators import responding_json, available_by_tvm, auth_by_tvm_only
from staff.lib.models.roles_chain import has_role

from staff.budget_position.const import WORKFLOW_STATUS
from staff.budget_position.forms import ExportChangesForm, ExportChangeRegistryForm
from staff.budget_position.models import ChangeRegistry
from staff.budget_position.workflow_service.entities.workflows import femida_workflows, proposal_workflows


def _has_access_to_export_changes(request) -> bool:
    yauser = request.yauser
    if isinstance(yauser, TvmServiceRequest):  # service already checked in decorator
        return True

    observer = request.user.get_profile()
    return has_role(observer, (DepartmentRoles.HR_ANALYST.value,))


def _make_changes_query(filter_form_data: Dict[str, Any]) -> Q:
    filters = {}
    if filter_form_data['from_id']:
        filters['id__gte'] = filter_form_data['from_id']
    if filter_form_data['from_date']:
        filters['workflow__confirmed_at__gte'] = filter_form_data['from_date']
    if filter_form_data['from_effective_date']:
        filters['effective_date__gte'] = filter_form_data['from_effective_date']

    export_workflows_codes = (
        femida_workflows.Workflow5_1.code,
        femida_workflows.Workflow5_2.code,
        proposal_workflows.Workflow7_1.code,
        proposal_workflows.Workflow7_100500.code,
        proposal_workflows.MoveWithoutBudgetPositionWorkflow.code
    )

    status_filter_q = Q(workflow__status__in=[WORKFLOW_STATUS.FINISHED, WORKFLOW_STATUS.SENDING_NOTIFICATION])
    request_filters_q = Q(**filters)
    workflow_code_filer_q = Q(workflow__code__in=export_workflows_codes)
    workflow53_filter_q = Q(workflow__code=femida_workflows.Workflow5_3.code, optional_ticket__isnull=False)
    filter_q = status_filter_q & request_filters_q & (workflow53_filter_q | workflow_code_filer_q)

    return filter_q


@require_GET
@available_by_tvm(['oebs-hrproc', 'staff'])
@responding_json
def export_changes(request):
    if not _has_access_to_export_changes(request):
        return HttpResponseForbidden()

    form = ExportChangesForm(request.GET)
    if not form.is_valid():
        return form.errors_as_dict(), 400

    changes = (
        ChangeRegistry.objects
        .filter(_make_changes_query(form.cleaned_data))
        .select_related('workflow', 'budget_position', 'staff')
        .order_by('id')[:100]
    )

    def serialize_change(change: ChangeRegistry) -> Dict[str, Any]:
        budget_position = None
        if change.budget_position:
            budget_position = {'code': change.budget_position.code, 'id': change.budget_position.id}

        budget_position_changed = change.workflow.code in (
            femida_workflows.Workflow5_3.code,
            proposal_workflows.MoveWithoutBudgetPositionWorkflow.code,
        )

        serialized = {
            'id': change.id,
            'budget_position': budget_position,
            'workflow_id': str(change.workflow_id),
            'push_status': change.push_status,
            'staff_id': change.staff_id,
            'oebs_transaction_id': change.oebs_transaction_id,
            'sent_to_oebs': change.sent_to_oebs and change.sent_to_oebs.isoformat()[:-7],
            'pushed_to_femida': change.pushed_to_femida,
            'remove_budget_position': change.remove_budget_position,
            'contract_term_date': change.contract_term_date,
            'contract_period': change.contract_period,
            'employment_type': change.employment_type,
            'instead_of_login': change.instead_of_login,
            'is_replacement': change.is_replacement,
            'join_at': change.join_at,
            'other_payments': change.other_payments,
            'person_id': change.person_id,
            'probation_period_code': change.probation_period_code,
            'budget_position_changed': budget_position_changed,
        }

        office_id = change.placement_id
        if change.office_id == settings.HOMIE_OFFICE_ID:
            office_id = -1  # https://st.yandex-team.ru/STAFF-13910#5f6a1142eaed4767d5765b13

        serialized.update({
            'proposal_id': change.workflow.proposal and change.workflow.proposal.proposal_id,
            'vacancy_id': change.workflow.vacancy_id,
            'started_at': change.workflow.created_at,
            'resolved_at': change.workflow.confirmed_at,
            'status': change.workflow.status,
            'meta': {
                'login': change.staff and change.staff.login,
                'office_id': office_id,
            },
        })

        fields = {
            'compensation_scheme_id',
            'currency',
            'department_id',
            'dismissal_date',
            'effective_date',
            'headcount',
            'organization_id',
            'pay_system',
            'wage_system',
            'placement_id',
            'position_id',
            'position_name',
            'position_type',
            'rate',
            'review_scheme_id',
            'salary',
            'ticket',
            'optional_ticket',
        }

        for field in fields:
            serialized['meta'][field] = getattr(change, field)

        if change.geo:
            geography = Geography.objects.filter(id=change.geo_id).first()
            serialized['meta']['geography_id'] = geography and geography.oebs_code
        else:
            serialized['meta']['geography_id'] = None

        serialized['meta']['grade_id'] = change.oebs_grade_id
        serialized['meta']['hr_product_id'] = change.staff_hr_product_id
        serialized['meta']['bonus_scheme_id'] = change.staff_bonus_scheme_id

        return serialized

    result = [serialize_change(change) for change in changes]
    return result


PAGE_SIZE = 5000


@require_GET
@auth_by_tvm_only(['staff'])
def export_change_registry(request):
    form = ExportChangeRegistryForm(request.GET)
    if not form.is_valid():
        return JsonResponse(data=form.errors_as_dict(), status=400)

    workflow_states = [
        WORKFLOW_STATUS.CONFIRMED,
        WORKFLOW_STATUS.QUEUED,
        WORKFLOW_STATUS.PUSHED,
        WORKFLOW_STATUS.SENDING_NOTIFICATION,
        WORKFLOW_STATUS.FINISHED,
    ]
    changes = ChangeRegistry.objects.filter(workflow__status__in=workflow_states)
    if form.cleaned_data.get('continuation_token') is not None:
        changes = changes.filter(workflow__confirmed_at__gte=form.cleaned_data['continuation_token'])

    changes = list(changes.select_related('workflow').order_by('workflow__confirmed_at', 'id')[:PAGE_SIZE])

    def serialize_change(change: ChangeRegistry) -> Dict[str, Any]:
        office_id = change.placement_id
        if change.office_id == settings.HOMIE_OFFICE_ID:
            office_id = -1  # https://st.yandex-team.ru/STAFF-13910#5f6a1142eaed4767d5765b13

        serialized = {
            'id': change.id,
            'budget_position_id': change.budget_position.id if change.budget_position else None,
            'budget_position_code': change.budget_position.code if change.budget_position else None,
            'workflow_id': str(change.workflow_id),
            'sent_to_oebs': change.sent_to_oebs and change.sent_to_oebs.isoformat()[:-7],
            'proposal_id': change.workflow.proposal and change.workflow.proposal.proposal_id,
            'vacancy_id': change.workflow.vacancy_id,
            'workflow_created_at': change.workflow.created_at,
            'workflow_confirmed_at': change.workflow.confirmed_at,
            'workflow_status': change.workflow.status,
            'workflow_code': change.workflow.code,
            'login': change.staff and change.staff.login,
            'office_id': office_id,
            'hr_product_id': change.staff_hr_product_id,
            'bonus_scheme_id': change.staff_bonus_scheme_id,
        }

        if change.geo:
            geography = Geography.objects.filter(id=change.geo_id).first()
            serialized['geography_id'] = geography and geography.oebs_code
        else:
            serialized['geography_id'] = None

        if not change.budget_position and change.new_budget_position:
            serialized['budget_position_id'] = change.new_budget_position.id
            serialized['budget_position_code'] = change.new_budget_position.code

        fields = {
            'ticket',
            'headcount',
            'department_id',
            'compensation_scheme_id',
            'currency',
            'dismissal_date',
            'effective_date',
            'organization_id',
            'wage_system',
            'placement_id',
            'position_id',
            'position_name',
            'position_type',
            'rate',
            'review_scheme_id',
            'salary',
            'optional_ticket',
            'probation_period_code',
            'person_id',
            'push_status',
            'staff_id',
            'oebs_transaction_id',
            'pushed_to_femida',
            'remove_budget_position',
            'contract_term_date',
            'contract_period',
            'employment_type',
            'instead_of_login',
            'is_replacement',
            'join_at',
            'other_payments',
            'oebs_grade_id',
        }

        for field in fields:
            serialized[field] = getattr(change, field)

        return serialized

    result = {'data': [serialize_change(change) for change in changes]}
    if len(changes) == PAGE_SIZE:
        result['continuation_token'] = changes[-1].workflow.confirmed_at

    return JsonResponse(data=result, status=200)
