import asyncio
import logging
from datetime import datetime, timezone
from typing import Callable

from smb.common.aiotvm import TvmClient
from maps_adv.geosmb.doorman.client import DoormanClient

from .data_manager import BaseDataManager
from .exceptions import NoPassportUidException


class Domain:
    __slots__ = (
        "_dm",
        "_tvm_client",
        "_doorman_client",
    )

    _dm: BaseDataManager
    _tvm_client: TvmClient
    _doorman_client: DoormanClient

    def __init__(
        self,
        dm: BaseDataManager,
        tvm_client: TvmClient,
        doorman_client: DoormanClient,
    ):
        self._dm = dm
        self._tvm_client = tvm_client
        self._doorman_client = doorman_client

    async def register_client_for_delete(self, tvm_user_ticket: str, request_id: str):
        passport_uid = await self._parse_passport_uid(tvm_user_ticket)

        await self._dm.register_client_for_delete(
            external_id=request_id, passport_uid=passport_uid
        )

    async def delete_clients(self):
        requests = await self._dm.list_not_processed_requests()

        for request in requests:
            processed_services = await self._dm.list_processed_services(
                request_id=request["id"]
            )

            clear_calls = []
            if "doorman" not in processed_services:
                clear_calls.append(
                    self._clear_doorman(
                        request_id=request["id"], passport_uid=request["passport_uid"]
                    )
                )

            if clear_calls:
                call_results = await asyncio.gather(*clear_calls)

                if all(call_results):
                    await self._dm.mark_request_as_processed(
                        record_id=request["id"], processed_at=datetime.now(timezone.utc)
                    )
            else:
                logging.getLogger(__name__).error(
                    f"Delete request is not marked as processed, "
                    f"but operations for all services are completed. "
                    f"request_id={request['id']}, "
                    f"processed_services={sorted(processed_services)}"
                )

    async def retrieve_client_status(self, tvm_user_ticket: str) -> str:
        passport_uid = await self._parse_passport_uid(tvm_user_ticket)

        last_request = await self._dm.retrieve_last_request(passport_uid)
        last_processed_dt = await self._dm.retrieve_dt_of_last_processed_request(
            passport_uid
        )
        if last_request and not last_request["processed_at"]:
            return "delete_in_progress", last_processed_dt

        calls_results = await asyncio.gather(
            self._doorman_client.search_clients_for_gdpr(passport_uid=passport_uid),
            return_exceptions=True,
        )

        success_results = [
            res for res in calls_results if not isinstance(res, Exception)
        ]
        calls_exceptions = [res for res in calls_results if isinstance(res, Exception)]

        if any(success_results):
            for exc in calls_exceptions:
                logging.getLogger(__name__).warning(
                    "Request to service failed, but we keep moving.",
                    exc_info=exc,
                )
            return "ready_to_delete", last_processed_dt
        elif calls_exceptions:
            raise calls_exceptions[0]
        else:
            return "empty", last_processed_dt

    async def _parse_passport_uid(self, tvm_user_ticket: str) -> int:
        passport_uid = await self._tvm_client.fetch_user_uid(ticket=tvm_user_ticket)
        if not passport_uid:
            raise NoPassportUidException

        return passport_uid

    async def _clear_doorman(self, request_id: int, passport_uid: int) -> bool:
        return await self._clear_service(
            clear_call=self._doorman_client.clear_clients_for_gdpr,
            request_id=request_id,
            passport_uid=passport_uid,
            service_name="doorman",
        )

    async def _clear_service(
        self,
        clear_call: Callable,
        request_id: int,
        passport_uid: int,
        service_name: str,
    ) -> bool:
        try:
            metadata = await clear_call(passport_uid=passport_uid)
            success = True
        except Exception as exc:
            logging.getLogger(__name__).exception(
                f"Failed to clear client's data in service. request_id={request_id}, "
                f"passport_uid={passport_uid}, service_name={service_name}.",
                exc_info=exc,
            )
            metadata = str(exc)
            success = False

        await self._dm.create_operation(
            request_id=request_id,
            service_name=service_name,
            metadata=metadata,
            is_success=success,
        )

        return success
