import uuid
from uuid import UUID

from psycopg import Connection
from src.__types.errors import ClientError
from src.__types.index import Distribution, Tenant
from src.domain.services.client_tenant_service import ClientTenantService
from src.infrastructure.credential_manager import CredentialManager
from src.infrastructure.repositories.client_tenant_repository import ClientTenantRepository
from src.infrastructure.repositories.pending_client_tenant_repository import PendingClientTenantRepository
from src.infrastructure.repositories.tenant_repository import TenantRepository
from src.logger import logger


class TenantService:
    """テナントに関するサービス"""

    def __init__(
        self,
        tenant_repository: TenantRepository,
        client_tenant_service: ClientTenantService,
        connection: Connection,
        credential_manager: CredentialManager,
    ) -> None:
        self.tenant_repository = tenant_repository
        self.client_tenant_service = client_tenant_service
        self.connection = connection
        self.credential_manager = credential_manager

    @classmethod
    def create(
        cls,
        connection: Connection,
        credential_manager: CredentialManager,
    ) -> "TenantService":
        tenant_repository = TenantRepository(connection=connection)
        client_tenant_repository = ClientTenantRepository(connection=connection)
        return TenantService(
            tenant_repository=tenant_repository,
            client_tenant_service=ClientTenantService(
                client_tenant_repository=client_tenant_repository,
                pending_client_tenant_repository=PendingClientTenantRepository(connection=connection),
                tenant_repository=tenant_repository,
            ),
            connection=connection,
            credential_manager=credential_manager,
        )

    def issue(
        self,
        legal_name: str,
        name: str | None,
        whi_customer_ids: list[str],
        cydas_customer_ids: list[str],
    ) -> Tenant:
        """Tenant発行 (Application Client作成・Tenant追加) を行う。"""
        if len(whi_customer_ids) == 0 and len(cydas_customer_ids) == 0:
            raise ClientError("whi_customer_id or cydas_customer_id are required.")

        tenant_id = uuid.uuid4()

        m2m_application_client_id = self.credential_manager.issue_m2m(tenant_id)

        try:
            with self.connection.transaction():
                self.tenant_repository.add(
                    tenant_id=tenant_id,
                    legal_name=legal_name,
                    name=name,
                    m2m_application_client_id=m2m_application_client_id,
                    whi_customer_ids=whi_customer_ids,
                    cydas_customer_ids=cydas_customer_ids,
                )
                self._transfer(
                    tenant_id=tenant_id,
                    whi_customer_ids=whi_customer_ids,
                    cydas_customer_ids=cydas_customer_ids,
                )
        except Exception:
            self._cleanup_application_client(application_client_id=m2m_application_client_id)
            raise

        return self.tenant_repository.get(tenant_id)

    def _cleanup_application_client(self, application_client_id: str) -> None:
        try:
            self.credential_manager.delete(application_client_id)
        except Exception:  # noqa: BLE001
            # Why: DB登録失敗後にCognito側の孤立クライアントが残ると運用負債になるため監視可能にする。
            logger.exception(
                "Failed to cleanup Cognito application client. application_client_id=%s",
                application_client_id,
            )

    def _transfer(
        self,
        tenant_id: UUID,
        whi_customer_ids: list[str],
        cydas_customer_ids: list[str],
    ) -> None:
        """PendingClientTenant→ClientTenantの移行を行う"""
        with self.connection.transaction():
            for customer_id in whi_customer_ids:
                self.client_tenant_service.transfer(
                    tenant_id=tenant_id,
                    distribution=Distribution.WHI_SFDC,
                    customer_id=customer_id,
                )
            for customer_id in cydas_customer_ids:
                self.client_tenant_service.transfer(
                    tenant_id=tenant_id,
                    distribution=Distribution.CYDAS_SFDC,
                    customer_id=customer_id,
                )
