import logging
from typing import Collection

from intranet.trip.src.api.schemas.document import PersonDocumentCreate
from intranet.trip.src.lib.utils import mask_value
from intranet.trip.src.unit_of_work import UnitOfWork
from intranet.trip.src.models import Document, Person
from intranet.trip.src.enums import Citizenship, DocumentType
from intranet.trip.src.lib.aeroclub.api import aeroclub
from intranet.trip.src.lib.aeroclub.enums import DocumentTypestringEnum
from intranet.trip.src.lib.aeroclub.models import ProfileDocumentIn


logger = logging.getLogger(__name__)


def fill_fio_in_passports(documents: list[dict], person):
    """
    Подмешиваем ФИО из нашей базы
    так как АК не хранит ФИО во внутренних паспортах РФ
    """
    for document in documents:
        if document['type'] == 'Passport':
            document['first_name'] = document['first_name'] or person.first_name_ac
            document['last_name'] = document['last_name'] or person.last_name_ac
            document['middle_name'] = person.middle_name_ac


def mask_passport_data(documents: list[dict]):
    for document in documents:
        document['series'] = mask_value(document['series'])
        document['number'] = mask_value(
            document['number'],
            visible_suffix_len=4,
        )


def mask_traveller_document(documents: list[dict]):
    for document in documents:
        document_number = document['document_number']
        if len(document_number.split(' ')) == 1:
            document['document_number'] = mask_value(document_number, visible_suffix_len=4)
        else:
            series, number = document_number.split(' ')
            series = mask_value(series)
            number = mask_value(number, visible_suffix_len=4)
            document['document_number'] = f'{series} {number}'


async def find_document_duplicates(documents: Collection[Document]) -> list[Document]:
    """
    Ищем дубликаты среди документов пользователя.
    2 документа считаем одинаковыми, если у них совпадает комбинация type, series, number.
    При этом оригиналом считаем тот, у которого больше document_id (был добавлен последним).
    """
    duplicates = []
    documents = list(documents)
    documents.sort(key=lambda x: x.document_id, reverse=True)
    used_keys = set()
    for document in documents:
        key = (document.document_type, document.series, document.number)
        if key in used_keys:
            duplicates.append(document)
        else:
            used_keys.add(key)
    return duplicates


async def find_extra_russian_passports(documents: Collection[Document]) -> list[Document]:
    duplicates = []
    documents = list(documents)
    documents.sort(key=lambda x: x.document_id, reverse=True)
    found = False
    for document in documents:
        if document.is_russian_passport:
            if not found:
                found = True
            else:
                duplicates.append(document)
    return duplicates


async def delete_person_document_duplicates(uow: UnitOfWork, person_ids: list[int]) -> int:
    deleted_count = 0
    for i, person_id in enumerate(person_ids):
        # TODO: вообще тут нужно использовать PersonalDataGateway
        documents = await uow.documents.get_documents(person_id)
        person_duplicates = await find_document_duplicates(documents)
        for duplicate in person_duplicates:
            await uow.documents.delete(duplicate.document_id)
            deleted_count += 1
    return deleted_count


# TODO: Можно сделать через get_references c кешированием либо уже в рамках BTRIP-2206
CITIZENSHIP_COUNTRY_MAPPING = {
    Citizenship.RU: 156,
    Citizenship.BY: 10,
    Citizenship.UA: 167,
    Citizenship.KZ: 28,
    Citizenship.KG: 27,
    Citizenship.AM: 3,
    Citizenship.UZ: 112,
    Citizenship.LV: 31,
    Citizenship.TJ: 52,
}


async def add_aeroclub_document(
    provider_profile_id: int,
    document: PersonDocumentCreate | Document,
) -> int:
    """
    Добавляем в АК, получаем document_id, сохраняем его в базу
    """
    if document.document_type == DocumentType.passport and document.citizenship == Citizenship.RU:
        document_type = DocumentTypestringEnum.passport
    else:
        document_type = DocumentTypestringEnum.national_passport
    document = ProfileDocumentIn(
        type=document_type,
        series=document.series,
        number=document.number,
        first_name=document.first_name,
        last_name=document.last_name,
        citizenship_country_id=CITIZENSHIP_COUNTRY_MAPPING.get(document.citizenship),
        issued_on=document.issued_on,
        expires_on=document.expires_on,
    )
    provider_document_id = await aeroclub.add_document(
        profile_id=provider_profile_id,
        document=document,
    )
    return provider_document_id


class AeroclubDocumentsSync:
    """
    Приводит в соответствие состояние документов в Командировках и Аэроклубе.
    """
    def __init__(self, uow: UnitOfWork, person: Person, dry_run=False, ):
        self.uow = uow
        self.person = person
        self.dry_run = dry_run
        self._trip_documents_by_id: dict[int, Document] = {}
        self._provider_documents_by_id: dict[int, dict] = {}
        self._to_delete_in_trip: list[int] = []
        self._to_delete_in_aeroclub: list[int] = []
        self._to_create_in_aeroclub: list[Document] = []
        self._bind_mapping: dict[int, int] = {}

    @property
    def trip_documents(self):
        return self._trip_documents_by_id.values()

    def _process_duplicates(self, duplicates: Collection[Document]):
        for duplicate in duplicates:
            self._to_delete_in_trip.append(duplicate.document_id)
            if duplicate.provider_document_id:
                self._to_delete_in_aeroclub.append(duplicate.provider_document_id)
            del self._trip_documents_by_id[duplicate.document_id]

    async def remove_document_duplicates(self):
        duplicates = await find_document_duplicates(self.trip_documents)
        self._process_duplicates(duplicates)

    async def remove_extra_russian_passports(self):
        duplicates = await find_extra_russian_passports(self.trip_documents)
        self._process_duplicates(duplicates)

    async def process_documents_with_provider_id(self):
        for document in self.trip_documents:
            if not document.provider_document_id:
                continue
            if document.provider_document_id not in self._provider_documents_by_id:
                self._to_create_in_aeroclub.append(document)

    def _find_document_in_aeroclub(self, document: Document) -> int | None:
        for ac_document in self._provider_documents_by_id.values():
            if document.is_russian_passport:
                is_type_matches = ac_document['type'] == DocumentTypestringEnum.passport
            else:
                is_type_matches = ac_document['type'] == DocumentTypestringEnum.national_passport

            if (
                is_type_matches
                and ac_document['series'] == document.series
                and ac_document['number'] == document.number
            ):
                return ac_document['id']

    async def process_documents_without_provider_id(self):
        for document in self.trip_documents:
            if document.provider_document_id:
                continue
            provider_document_id = self._find_document_in_aeroclub(document)
            if provider_document_id:
                self._bind_mapping[document.document_id] = provider_document_id
            else:
                self._to_create_in_aeroclub.append(document)

    async def delete_documents_in_aeroclub(self):
        logger.info('Documents to delete in Aeroclub: %s', self._to_delete_in_aeroclub)
        if self.dry_run:
            return
        for document_id in self._to_delete_in_aeroclub:
            await aeroclub.delete_document(
                profile_id=self.person.provider_profile_id,
                document_id=document_id,
            )

    async def create_documents_in_aeroclub(self):
        logger.info(
            'Documents to create in Aeroclub: %s',
            [d.document_id for d in self._to_create_in_aeroclub],
        )
        if self.dry_run:
            return
        for document in self._to_create_in_aeroclub:
            provider_document_id = await add_aeroclub_document(
                provider_profile_id=self.person.provider_profile_id,
                document=document,
            )
            self._bind_mapping[document.document_id] = provider_document_id

    async def delete_documents_in_trip(self):
        logger.info('Documents to delete in Trip: %s', self._to_delete_in_trip)
        if self.dry_run:
            return
        for document_id in self._to_delete_in_trip:
            await self.uow.documents.delete(document_id)

    async def bind_document_ids_in_trip(self):
        logger.info('Document ids mapping: %s', self._bind_mapping)
        if self.dry_run:
            return
        for document_id, provider_document_id in self._bind_mapping.items():
            await self.uow.documents.update(
                document_id=document_id,
                provider_document_id=provider_document_id,
            )

    async def sync(self):
        assert self.person.provider_profile_id is not None

        # pdg: PersonalDataGateway = await get_personal_data_gateway(uow)
        # trip_documents = await pdg.get_person_documents(person_id=person.person_id, user=user)
        trip_documents = await self.uow.documents.get_documents(self.person.person_id)
        self._trip_documents_by_id = {
            doc.document_id: doc for doc
            in trip_documents
            if doc.citizenship != Citizenship.OTHER
        }

        profile = await aeroclub.get_profile(self.person.provider_profile_id)
        self._provider_documents_by_id = {doc['id']: doc for doc in profile['documents']}

        # В этих методах только ищем что удалять/создавать
        await self.remove_document_duplicates()
        await self.remove_extra_russian_passports()
        await self.process_documents_with_provider_id()
        await self.process_documents_without_provider_id()

        # В этих методах непосредственно создаем/удаляем/связываем в АК и базе
        await self.delete_documents_in_aeroclub()
        await self.create_documents_in_aeroclub()
        await self.delete_documents_in_trip()
        await self.bind_document_ids_in_trip()
