from uuid import UUID

from botocore.exceptions import BotoCoreError, ClientError
from src.__types.errors import IdentityProviderError, ResourceDuplicatedError
from src.__types.index import Tenant, User
from src.infrastructure.clients.cognito_idp_client import CognitoIdpClientProtocol
from src.logger import logger

_USERNAME_PREFIX = "USR_"


class UserIdentityRepository:
    """サービス管理のID基盤のユーザーを管理するリポジトリ"""

    def __init__(self, tenant: Tenant, client: CognitoIdpClientProtocol) -> None:
        self.tenant = tenant
        self.client = client

    def create_user(self, user: User) -> None:
        """ユーザー情報をもとにサービス管理のID基盤のユーザーを作成する。"""
        try:
            self.client.create_user(
                params={
                    "tenant_id": str(self.tenant.id),
                    "user_id": str(user.id),
                    "username": self._build_username(user_id=user.id),
                    "preferred_username": self._build_preferred_username(user_display_id=user.display_id),
                    "email": user.email,
                },
            )
        except ClientError as e:
            self._raise_if_alias_exists(error=e)
            raise IdentityProviderError(f"Failed to create user. user_id: '{user.id}'") from e
        except BotoCoreError as e:
            raise IdentityProviderError(f"Failed to create user. user_id: '{user.id}'") from e

    def update_user(self, user: User) -> None:
        """ユーザー情報をもとにサービス管理のID基盤のユーザーを更新する。"""
        try:
            self.client.update_user(
                params={
                    "username": self._build_username(user_id=user.id),
                    "preferred_username": self._build_preferred_username(user_display_id=user.display_id),
                    "email": user.email,
                },
            )
        except ClientError as e:
            self._raise_if_alias_exists(error=e)
            raise IdentityProviderError(f"Failed to update user. user_id: '{user.id}'") from e
        except BotoCoreError as e:
            raise IdentityProviderError(f"Failed to update user. user_id: '{user.id}'") from e

    def delete_user(self, user_id: UUID) -> None:
        """ユーザーIDをもとにサービス管理のID基盤のユーザーを削除する。"""
        try:
            self.client.delete_user(username=self._build_username(user_id=user_id))
        except ClientError as e:
            if e.response["Error"]["Code"] == "UserNotFoundException":
                logger.warning("User not found. It may have been already deleted.", user_id=user_id)
                return
            raise IdentityProviderError(f"Failed to delete user. user_id: '{user_id}'") from e
        except BotoCoreError as e:
            raise IdentityProviderError(f"Failed to delete user. user_id: '{user_id}'") from e

    def _build_username(self, user_id: UUID) -> str:
        """ユーザーを一意に扱うため、tenant_idとuser_idを含めたusernameを構築する。"""
        return f"{_USERNAME_PREFIX}{self.tenant.id}_{user_id}"

    def _build_preferred_username(self, user_display_id: str) -> str:
        """ユーザーの表示IDをpreferred_usernameとして設定する。"""
        return f"{self.tenant.id}_{user_display_id}"

    def _raise_if_alias_exists(self, error: ClientError) -> None:
        """AliasExistsExceptionが発生した場合、ResourceDuplicatedErrorを発生させる。

        すでに同じ preferred_username が存在する場合は、ユーザーの display_id が重複しているとみなし、
        ResourceDuplicatedError を発生させる。
        """
        if error.response["Error"]["Code"] == "AliasExistsException":
            # display_id に個人情報が含まれる可能性があるため、preferred_username と表現する
            raise ResourceDuplicatedError(
                f"User preferred_username for tenant ID {self.tenant.id} already exists in UserPool.",
                duplicated_field="display_id",
            ) from error
