from itertools import groupby
import logging
from typing import Dict, List, Set
import yenv

import attr

from staff.departments.models import Department, DepartmentStaff, DepartmentRoles, Vacancy
from staff.person.models import Staff
from staff.proposal.controllers import ProposalTasks
from staff.proposal.proposal_builder import ProposalBuilder
from staff.proposal.models import ProposalMetadata
from staff.proposal.tasks import ExecuteProposal


logger = logging.getLogger(__name__)


@attr.s(auto_attribs=True)
class RolesInDepartment:
    chief_id: int or None
    deputies_id: Set[int]


DeparmentsWithRoles = Dict[str, RolesInDepartment]


def _roles_in_departments(person: Staff) -> DeparmentsWithRoles:
    roles_id = (DepartmentRoles.CHIEF.value, DepartmentRoles.DEPUTY.value)
    departments_qs = (
        Department.objects
        .filter(departmentstaff__staff_id=person.id, departmentstaff__role_id__in=roles_id)
    )
    qs = (
        DepartmentStaff.objects
        .filter(role_id__in=roles_id, department__in=departments_qs)
        .order_by('department__tree_id', 'department__lft')
        .values_list('department__url', 'staff_id', 'role_id')
    )
    result: DeparmentsWithRoles = {}
    for url, department_staff in groupby(qs, lambda ds: ds[0]):
        roles_in_department = RolesInDepartment(chief_id=None, deputies_id=set())
        for _, person_id, role_id in department_staff:
            if role_id == DepartmentRoles.CHIEF.value:
                roles_in_department.chief_id = person_id
            elif role_id == DepartmentRoles.DEPUTY.value:
                roles_in_department.deputies_id.add(person_id)
        result[url] = roles_in_department
    return result


def _remove_person_crowns(person_id: int, departments_with_roles: DeparmentsWithRoles) -> DeparmentsWithRoles:
    result: DeparmentsWithRoles = {}
    for url, roles_in_department in departments_with_roles.items():
        chief_id = None if roles_in_department.chief_id == person_id else roles_in_department.chief_id
        deputies_id = set(roles_in_department.deputies_id)
        deputies_id.discard(person_id)
        result[url] = RolesInDepartment(chief_id, deputies_id)
    return result


def _deputies_by_department_url(departments_url: List[str]) -> Dict[str, List[int]]:
    qs = (
        DepartmentStaff.objects
        .filter(role_id=DepartmentRoles.DEPUTY.value, department__url__in=departments_url)
        .order_by('department__url')
        .values_list('department__url', 'staff__id')
    )

    deputies_by_department_url = {}
    for key, persons_id in groupby(qs, lambda pair: pair[0]):
        deputies_by_department_url[key] = [person_id for _, person_id in persons_id]
    return deputies_by_department_url


def _salary_ticket_for_rotation(offer_id) -> str:
    salary_ticket = Vacancy.objects.get(offer_id=offer_id).salary_ticket
    assert salary_ticket
    return salary_ticket


def _create_proposals(
    roles_in_departments: DeparmentsWithRoles,
    adopter_login: str,
    salary_ticket: str,
) -> List[str]:
    assert adopter_login
    assert salary_ticket

    result = []
    for dep_url, roles in roles_in_departments.items():
        proposal_id = (
            ProposalBuilder()
            .for_existing_department(
                dep_url,
                lambda dep_conf: dep_conf.change_roles(roles.chief_id, list(roles.deputies_id))
            )
            .with_linked_ticket(salary_ticket)
            .build(adopter_login)
        )
        result.append(proposal_id)

    return result


def _schedule_proposal_execution(proposal_id: str, adopter_login: str):
    if yenv.type == 'development':
        ExecuteProposal(proposal_id, adopter_login)
        return

    ProposalTasks.schedule_ordered_task(
        ProposalMetadata.objects.get(proposal_id=proposal_id).id,
        callable_or_task=ExecuteProposal,
        kwargs={'proposal_id': proposal_id, 'author_login': adopter_login},
    )


def deprive_gold_and_silver_crown_through_proposal(adopter: Staff, person: Staff, offer_id: int):
    roles_in_departments = _roles_in_departments(person)

    if not roles_in_departments:
        return

    roles_in_departments = _remove_person_crowns(person.id, roles_in_departments)
    proposal_ids = _create_proposals(roles_in_departments, adopter.login, _salary_ticket_for_rotation(offer_id))

    logger.info('Created proposals %s to deprive crowns', proposal_ids)

    for proposal_id in proposal_ids:
        _schedule_proposal_execution(proposal_id, adopter.login)
