from abc import ABCMeta, abstractmethod
from async_clients.clients.base import BaseClient
from async_clients.auth_types import TVM2

from intranet.trip.src.lib.passport.exceptions import PassportApiError
from intranet.trip.src.lib.passport.models import (
    Contact,
    Document,
    NationalPassport,
    InternationalPassport,
)


class BaseDocumentsApiClient(BaseClient):
    __metaclass__ = ABCMeta

    AUTH_TYPES = None

    @abstractmethod
    async def add(self, user_id: str, document: Document) -> str:
        raise NotImplementedError

    @abstractmethod
    async def update(self, document: Document) -> str:
        raise NotImplementedError

    @abstractmethod
    async def delete(self, document_id: str) -> str:
        raise NotImplementedError

    @abstractmethod
    async def get(self, document_id: str) -> Document:
        raise NotImplementedError

    @abstractmethod
    async def list(self, user_id: str, length: int = 5, offset: int = 0) -> list[Document]:
        raise NotImplementedError


class DocumentsApiClient(BaseDocumentsApiClient):
    """
    Работа с документами через ручки паспорта
    Документация: https://wiki.yandex-team.ru/passport/documents/
    """
    AUTH_TYPES = {TVM2}

    async def add(self, user_id: str, document: Document) -> str:
        """
        Add document to Passport

        :param user_id: uid
        :param document: document for sync
        :return document_id if success
        """
        # FIXME: after passport will be released
        response = await self._make_request(
            method='post',
            path='document/add',
            params={
                'user_id': user_id,
                'user_type': 'uid',
            },
            json=document.dict(),
        )
        if response['status'] == 'ok':
            return response['id']
        raise PassportApiError('Document has not been added')

    async def update(self, document: Document) -> str:
        """
        Update document in passport
        :param document: document for sync
        :return: True if success
        """
        # FIXME: after passport will be released
        response = await self._make_request(
            method='post',
            path='document/update',
            params={
                'id': document.id,
            },
            json=document.dict(),
        )
        if response['status'] == 'ok':
            return response['id']
        raise PassportApiError('Document has not been updated')

    async def delete(self, document_id: str) -> str:
        """
        Delete document
        :param document_id: id document in passport
        :return: document_id if success
        """
        response = await self._make_request(
            method='get',
            path='document/delete',
            params={
                'id': document_id,
            },
        )
        if response['status'] == 'ok':
            return response['id']
        raise PassportApiError('Document has not been updated')

    async def get(self, document_id: str) -> Document:
        """
        Get document from passport
        :param document_id: id document in passport
        :return document if success
        """
        # FIXME: after passport will be released
        response = await self._make_request(
            method='get',
            path='document/get',
            params={
                'id': document_id,
            },
        )
        if response['status'] != 'ok':
            raise PassportApiError('Document has not been get')
        if response['passport_types'] == 'national':
            return NationalPassport(**response)
        if response['passport_types'] == 'international':
            return InternationalPassport(**response)
        raise PassportApiError('Unknown passport types')

    async def list(
        self,
        user_id: str,
        length: int = 10,
        offset: int = 0,
    ) -> list[Document]:
        response = await self._make_request(
            method='get',
            path='document/list',
            params={
                'user_id': user_id,
                'offset': offset,
                'length': length,
                'user_type': 'uid',
            },
        )
        if response['status'] == 'ok':
            return [Document(**item) for item in response['documents']]
        raise PassportApiError('Documents has not been get')


class BaseContactsApiClient(BaseClient):
    __metaclass__ = ABCMeta

    AUTH_TYPES = None

    @abstractmethod
    async def add(self, user_id: str, contact: Contact) -> str:
        raise NotImplementedError

    @abstractmethod
    async def get(self, user_id: str, contact_id: str) -> Contact:
        raise NotImplementedError

    @abstractmethod
    async def update(self, user_id: str, contact: Contact) -> str:
        raise NotImplementedError

    @abstractmethod
    async def delete(self, user_id: str, contact_id: str):
        raise NotImplementedError

    @abstractmethod
    async def list(self, user_id: str, length: int = 5, offset: int = 0) -> list[Contact]:
        raise NotImplementedError


class ContactsApiClient(BaseContactsApiClient):
    """
    Работа с контактами через ручки паспорта
    Документация: https://wiki.yandex-team.ru/passport/address/#kontaktydljadostavkivpasporte
    """
    AUTH_TYPES = {TVM2}

    async def add(self, user_id: str, contact: Contact) -> str:
        """
        Add contact to Passport API
        Email or phone_number should be not empty

        :param user_id: uid
        :param contact: description of contact
        :return: contact_external_id if success
        """
        response = await self._make_request(
            method='post',
            path='contact/create',
            params={
                'user_id': user_id,
                'user_type': 'uid',
            },
            json=contact.dict(),
        )
        if response['status'] == 'ok':
            return response['id']
        raise PassportApiError('Contact has not been added')

    async def get(self, user_id: str, contact_id: str) -> Contact:
        """
        Get contact for user from Passport by contact_id

        :param user_id:
        :param contact_id:
        :return: contact if success
        """
        response = await self._make_request(
            method='get',
            path='contact/get',
            params={
                'id': contact_id,
                'user_id': user_id,
                'user_type': 'uid',
            },
        )
        if response['status'] == 'ok':
            return Contact(**response)
        raise PassportApiError('Contact has not been get')

    async def update(self, user_id: str, contact: Contact) -> str:
        """
        Update contact for user, contact fields should be correct

        :param user_id: uid
        :param contact: id, first_name, last_name is required fields
        :return:
        """
        response = await self._make_request(
            method='post',
            path='contact/update',
            params={
                'user_id': user_id,
                'user_type': 'uid',
            },
            json=contact.dict(),
        )
        if response['status'] == 'ok':
            return response['id']
        raise PassportApiError('Contact has not been updated')

    async def delete(self, user_id: str, contact_id: str):
        """
        Delete contact for user
        """
        await self._make_request(
            method='get',
            path='contact/delete',
            params={
                'id': contact_id,
                'user_id': user_id,
                'user_type': 'uid',
            },
        )

    async def list(
        self,
        user_id: str,
        length: int = 5,
        offset: int = 0,
    ) -> list[Contact]:
        """
        Get list of contacts for user
        :param user_id: - uid
        :param length: - number of contacts in response
        :param offset: - offset
        :return - list of contacts
        """
        response = await self._make_request(
            method='get',
            path='contact/list',
            params={
                'user_id': user_id,
                'offset': offset,
                'length': length,
                'user_type': 'uid',
            },
        )
        if response['status'] == 'ok':
            # TODO: after fix delete contacts and remove sorted method
            return sorted([
                Contact(**item)
                for item in response['contacts']
                if item['owner_service'] == 'tools_trip'  # filter contacts trip-service
            ], key=lambda c: c.id)
        raise PassportApiError('Contacts have not been get')
