from collections.abc import Callable

from psycopg import Connection
from pydantic import EmailStr
from src.__types.errors import AlreadyInitializedError, ClientError, ResourceNotFoundError
from src.__types.index import Distribution, Tenant
from src.domain.services.management_console_service import ManagementConsoleService
from src.domain.services.user_service import UserService
from src.infrastructure.credential_manager import CredentialManager
from src.infrastructure.database.execute_tenant_rls import execute_tenant_rls
from src.infrastructure.repositories.tenant_repository import TenantRepository
from src.infrastructure.repositories.user_identity_repository import UserIdentityRepository
from src.infrastructure.repositories.user_repository import UserRepository


class ManagementConsoleUseCase:
    def __init__(
        self,
        connection: Connection,
        tenant_repository: TenantRepository,
        credential_manager: CredentialManager,
        user_identity_repository_factory: Callable[[Tenant], UserIdentityRepository],
    ) -> None:
        self.connection = connection
        self.tenant_repository = tenant_repository
        self.credential_manager = credential_manager
        # Why: tenantは_resolve_tenantで初めて確定するため、インスタンス化時点では
        # UserIdentityRepositoryを生成できない。遅延生成のためfactoryで受け取る。
        self.user_identity_repository_factory = user_identity_repository_factory

    @classmethod
    def create(
        cls,
        connection: Connection,
        credential_manager: CredentialManager,
        user_identity_repository_factory: Callable[[Tenant], UserIdentityRepository],
    ) -> "ManagementConsoleUseCase":
        return ManagementConsoleUseCase(
            connection=connection,
            tenant_repository=TenantRepository(connection=connection),
            credential_manager=credential_manager,
            user_identity_repository_factory=user_identity_repository_factory,
        )

    def initialize(
        self,
        whi_customer_ids: list[str],
        cydas_customer_ids: list[str],
        tenant_name: str,
        user_emails: list[EmailStr],
    ) -> None:
        """管理画面の初期化を行う。"""
        if len(whi_customer_ids) == 0 and len(cydas_customer_ids) == 0:
            raise ClientError("whi_customer_id or cydas_customer_id are required.")

        tenant = self._resolve_tenant(
            whi_customer_ids=whi_customer_ids,
            cydas_customer_ids=cydas_customer_ids,
        )
        self._validate_not_initialized(tenant=tenant, tenant_name=tenant_name)
        execute_tenant_rls(connection=self.connection, tenant_id=tenant.id)

        user_identity_repository = self.user_identity_repository_factory(tenant)
        management_console_service = ManagementConsoleService(
            tenant_repository=self.tenant_repository,
            credential_manager=self.credential_manager,
            user_service=UserService(
                user_repository=UserRepository(tenant=tenant, connection=self.connection),
                user_identity_repository=user_identity_repository,
            ),
        )
        management_console_service.initialize(
            tenant=tenant,
            tenant_name=tenant_name,
            user_emails=user_emails,
        )

    def _resolve_tenant(
        self,
        whi_customer_ids: list[str],
        cydas_customer_ids: list[str],
    ) -> Tenant:
        """取引先IDからテナントを特定する。"""
        # 取引先IDが複数テナントにまたがることはないため、先頭の1件で特定する。
        for customer_id in whi_customer_ids:
            return self.tenant_repository.get_by_customer_id(
                distribution=Distribution.WHI_SFDC,
                customer_id=customer_id,
            )

        for customer_id in cydas_customer_ids:
            return self.tenant_repository.get_by_customer_id(
                distribution=Distribution.CYDAS_SFDC,
                customer_id=customer_id,
            )

        raise ResourceNotFoundError("Tenant not found.")  # 型安全のために例外を投げる

    def _validate_not_initialized(self, tenant: Tenant, tenant_name: str) -> None:
        """管理画面が未開通であることを検証する。

        Raises:
            - AlreadyInitializedError: 管理画面が開通済み

        """
        if tenant.user_application_client_id is None:
            return

        if tenant.name == tenant_name:
            raise AlreadyInitializedError("Management console is already initialized with the same name.")

        raise AlreadyInitializedError(
            f"Tenant is already initialized with a different name: '{tenant.name}'.",
        )
