import logging
from abc import ABC, abstractmethod

from intranet.trip.src.api.schemas import (
    PersonDetailsUpdate,
    PersonDocumentCreate,
)
from intranet.trip.src.config import settings
from intranet.trip.src.enums import Citizenship, DocumentType
from intranet.trip.src.exceptions import WorkflowError
from intranet.trip.src.lib.aeroclub.api import aeroclub
from intranet.trip.src.lib.hub.sync import HUB_DOCUMENT_TYPES
from intranet.trip.src.lib.passport import (
    Contact,
    PassportApiError,
    PassportApiGateway,
)
from intranet.trip.src.lib.translit.ru_icao import ICAORussianLanguagePack
from intranet.trip.src.lib.utils import mask_value
from intranet.trip.src.models import (
    Document,
    Person,
    User,
)
from intranet.trip.src.unit_of_work import UnitOfWork
from intranet.trip.src.logic.aeroclub.documents import add_aeroclub_document


logger = logging.getLogger(__name__)


class PersonalDataGateway(ABC):
    def __init__(self, uow: UnitOfWork = None):
        self.uow = uow

    @abstractmethod
    async def get_person(self, person_id: int) -> Person:
        """
        Get user by person_id

        :param person_id:
        :return: object of Person model
        """
        raise NotImplementedError

    @abstractmethod
    async def update_person(
        self,
        person_id: int,
        person_details: PersonDetailsUpdate,
    ) -> None:
        """
        Update (or create) personal data by person_id

        :param person_id:
        :param person_details: details for update
        :return:
        """
        raise NotImplementedError

    @abstractmethod
    async def create_person_document(
        self,
        person: Person,
        document_create: PersonDocumentCreate,
    ) -> int:
        """
        Create person document

        :param person:
        :param document_create: document attributes
        :return: document_id
        """
        raise NotImplementedError

    @abstractmethod
    async def get_person_documents(
        self,
        person_id: int,
        user: User,
    ) -> list[Document]:
        """
        Get a list of Person documents on behalf of the User

        :param person_id: whose documents
        :param user: who requests
        :return: list of documents
        """
        raise NotImplementedError

    @abstractmethod
    async def delete_person_document(self, person: Person, document_id: int) -> None:
        raise NotImplementedError


class PersonalDataMixin:
    def __init__(self, uow: UnitOfWork = None):
        self.uow = uow

    async def is_person_exists(self, uid: str) -> bool:
        """
        Return True if person exist in trip-DB
        """
        return await self.uow.persons.is_person_exists(uid)

    async def get_user_by_uid(self, uid: str) -> User | None:
        """
        Get user by uid

        :param uid:
        :return: object of Person model
        """
        return await self.uow.persons.get_user_by_uid(uid)


class InternalPersonalData(PersonalDataGateway, PersonalDataMixin):
    """
    Gateway for personal data stored entirely inside trip DB
    """
    async def get_person(self, person_id: int) -> Person:
        return await self.uow.persons.get_person(person_id)

    async def update_person(self, person_id: int, person_details: PersonDetailsUpdate) -> None:
        person_data = person_details.dict(exclude_none=True)
        if not person_data:
            return

        async with self.uow:
            await self.uow.persons.update(person_id=person_id, **person_data)
            self.uow.add_job(
                job_name='sync_profiles_to_ihub_task',
                person_ids=[person_id],
                unique=False,
            )

    async def create_person_document(
        self,
        person: Person,
        document_create: PersonDocumentCreate,
    ) -> int:
        async with self.uow:
            document_id = await self.uow.documents.create(
                person_id=person.person_id,
                **document_create.dict(),
            )
            if (
                document_create.citizenship == Citizenship.RU
                and document_create.document_type == DocumentType.passport
            ):
                person_data_from_document = {
                    'first_name_ac': document_create.first_name,
                    'last_name_ac': document_create.last_name,
                    'middle_name_ac': document_create.middle_name,
                }
                transliterator = ICAORussianLanguagePack()

                middle_name_ac_en = None
                if document_create.middle_name:
                    middle_name_ac_en = transliterator.translit(
                        document_create.middle_name,
                        reversed=True,
                    )

                person_data_from_document.update({
                    'first_name_ac_en': transliterator.translit(
                        document_create.first_name,
                        reversed=True,
                    ),
                    'last_name_ac_en': transliterator.translit(
                        document_create.last_name,
                        reversed=True,
                    ),
                    'middle_name_ac_en': middle_name_ac_en,
                })

                await self.uow.persons.update(person.person_id, **person_data_from_document)

            if settings.ENABLE_AEROCLUB_API_DOCUMENTS_SYNC:
                provider_document_id = await add_aeroclub_document(
                    provider_profile_id=person.provider_profile_id,
                    document=document_create,
                )
                await self.uow.documents.update(
                    document_id=document_id,
                    provider_document_id=provider_document_id,
                )

            self.uow.add_job(
                job_name='sync_profiles_to_ihub_task',
                person_ids=[person.person_id],
                unique=False,
            )
        return document_id

    async def get_person_documents(
        self,
        person_id: int,
        user: User,
    ) -> list[Document]:
        documents = await self.uow.documents.get_documents(person_id)

        if user.person_id != person_id:
            for document in documents:
                document.series = mask_value(document.series)
                document.number = mask_value(
                    document.number,
                    visible_suffix_len=4,
                )
        return documents

    async def delete_person_document(self, person: Person, document_id: int) -> None:
        async with self.uow:
            document = await self.uow.documents.get_document(document_id)
            await self.uow.documents.delete(document_id)

            if document.document_type in HUB_DOCUMENT_TYPES:
                if settings.ENABLE_AEROCLUB_API_DOCUMENTS_SYNC:
                    await aeroclub.delete_document(
                        profile_id=person.provider_profile_id,
                        document_id=document_id,
                    )
                else:
                    self.uow.add_job(
                        job_name='sync_profiles_to_ihub_task',
                        person_ids=[document.person_id],
                        unique=False,
                    )


class PassportPersonalData(PersonalDataGateway, PersonalDataMixin):
    """
    Get/set PersonalData from two storages: DB-trip and Passport:
    - store latin full name in Passport.Contact
    - store full name in Passport.DatUM
    - store documents in Passport.DatUM
    """
    def __init__(self, uow: UnitOfWork = None, passport: PassportApiGateway = None):
        super().__init__(uow)
        self.passport = passport

    @classmethod
    async def init(cls, uow: UnitOfWork = None):
        passport: PassportApiGateway = await PassportApiGateway.init()
        return cls(uow=uow, passport=passport)

    async def create_person(self, uid: str, email: str) -> int:
        """
        Create person and personal data for user

        :param uid: uid from blackbox/Passport
        :param email:
        :return: person_id from DB-trip
        """
        assert self.uow
        fields = {
            'uid': uid,
            'first_name': '',
            'last_name': '',
            'login': '',
            'external_uid': '',
            'is_coordinator': False,
            'is_dismissed': False,
            'is_active': True,
            'is_limited_access': False,
        }
        fields['person_id'] = await self.uow.persons.create(**fields)
        user = User(**fields)
        user.email = email
        await self.complement_user_contacts(user)
        return fields['person_id']

    async def get_person(self, person_id: int) -> Person:
        assert self.uow
        person = await self.uow.persons.get_person(person_id)
        contact = await self._get_or_create_contact(uid=person.uid)
        # TODO: get full name, date_of_birth, gender from Passport.DatUM (BTRIP-2934)
        # get latin full name
        person.first_name_en = contact.first_name
        person.first_name_ac_en = contact.first_name
        person.last_name_en = contact.last_name
        person.last_name_ac_en = contact.last_name
        person.middle_name_en = contact.second_name
        person.middle_name_ac_en = contact.second_name
        # get contacts
        person.email = contact.email
        person.phone_number = contact.phone_number
        return person

    async def update_person(self, person_id: int, person_details: PersonDetailsUpdate) -> None:
        assert self.uow
        person = await self.uow.persons.get_person(person_id)
        contacts = await self.passport.contacts.list(user_id=person.uid)
        if len(contacts) > 1:
            logger.warning('User %s has several contacts in Passport', person.uid)
            # TODO: after fix delete contacts and remove extra contacts uncomment next string
            # raise WorkflowError('Too many contacts for user')
        if len(contacts) == 0:
            # contact must be added during authorization process
            logger.error('Get empty list of contacts for user %s', person.uid)
            raise WorkflowError('Get empty list of contacts')
        contact = contacts[0]
        contact.first_name = person_details.first_name_ac_en
        contact.last_name = person_details.last_name_ac_en
        contact.second_name = person_details.middle_name_ac_en
        await self.passport.contacts.update(user_id=person.uid, contact=contact)
        # TODO: update date_of_birth in Passport.DatUM BTRIP-2934

    async def create_person_document(
        self,
        person: Person,
        document_create: PersonDocumentCreate,
    ) -> int:
        # TODO: get personal data from Passport BTRIP-2934
        raise NotImplementedError

    async def get_person_documents(self, person_id: int, user: User) -> list[Document]:
        # TODO: get personal data from Passport BTRIP-2934
        raise NotImplementedError

    async def delete_person_document(self, person: Person, document_id: int) -> None:
        # TODO: get personal data from Passport BTRIP-2934
        raise NotImplementedError

    async def _get_or_create_contact(
        self,
        uid: str,
        first_name: str = '',
        last_name: str = '',
        email: str = '',
        phone_number: str = '',
    ) -> Contact:
        contact = Contact(
            first_name=first_name,
            last_name=last_name,
            email=email,
            phone_number=phone_number,
        )
        contacts = await self.passport.contacts.list(user_id=uid)
        if len(contacts) > 1:
            logger.warning('User %s has several contacts in Passport', uid)
        if len(contacts) == 0:
            logger.warning('Contacts for the user %s were not retrieved from Passport', uid)
            try:
                await self.passport.contacts.add(user_id=uid, contact=contact)
            except PassportApiError as e:
                logger.warning('%s', e.message)
        else:
            contact = contacts[0]
        return contact

    async def complement_user_contacts(self, user: User) -> User:
        """
        Get contacts from Passport API and complement user object
        :param user:
        :return: user with phone_number and email
        """
        contact = await self._get_or_create_contact(
            uid=user.uid,
            first_name=user.first_name,
            last_name=user.last_name,
            email=user.email,
            phone_number=user.phone_number,
        )
        user.phone_number = contact.phone_number
        user.email = contact.email
        return user


async def get_personal_data_gateway(uow: UnitOfWork) -> PassportPersonalData | InternalPersonalData:
    if not settings.IS_YA_TEAM:
        return await PassportPersonalData.init(uow)
    return InternalPersonalData(uow)


async def complement_user_contacts(user: User) -> User:
    if not settings.IS_YA_TEAM:
        pdg: PassportPersonalData = await PassportPersonalData.init()
        return await pdg.complement_user_contacts(user)
    return user
