import uuid
from typing import Protocol, TypedDict

import boto3
from botocore.config import Config
from src.logger import logger


class UserPoolClientParams(TypedDict, total=False):
    GenerateSecret: bool
    RefreshTokenValidity: int
    AccessTokenValidity: int
    IdTokenValidity: int
    TokenValidityUnits: dict[str, str]
    EnableTokenRevocation: bool
    AllowedOAuthFlowsUserPoolClient: bool
    AllowedOAuthFlows: list[str]
    AllowedOAuthScopes: list[str]
    ExplicitAuthFlows: list[str]
    ReadAttributes: list[str]
    WriteAttributes: list[str]
    SupportedIdentityProviders: list[str]
    PreventUserExistenceErrors: str


class CreateUserParams(TypedDict):
    tenant_id: str
    user_id: str
    username: str
    preferred_username: str
    email: str | None


class UpdateUserParams(TypedDict):
    username: str
    preferred_username: str
    email: str | None


class CognitoIdpClientProtocol(Protocol):
    user_pool_id: str

    def create_user_pool_client(self, client_name: str, params: UserPoolClientParams) -> str: ...
    def delete_user_pool_client(self, application_client_id: str) -> None: ...
    def create_user(self, params: CreateUserParams) -> None: ...
    def update_user(self, params: UpdateUserParams) -> None: ...
    def delete_user(self, username: str) -> None: ...


class CognitoIdpClient:
    def __init__(self, user_pool_id: str) -> None:
        retry_config = Config(retries={"max_attempts": 3, "mode": "standard"})
        self.client = boto3.client("cognito-idp", config=retry_config)
        self.user_pool_id = user_pool_id

    def create_user_pool_client(
        self,
        client_name: str,
        params: UserPoolClientParams,
    ) -> str:
        response = self.client.create_user_pool_client(
            UserPoolId=self.user_pool_id,
            ClientName=client_name,
            **params,
        )
        logger.debug("Cognito user pool client created.", client_name=client_name, user_pool_id=self.user_pool_id)
        return response["UserPoolClient"]["ClientId"]

    def delete_user_pool_client(self, application_client_id: str) -> None:
        self.client.delete_user_pool_client(
            UserPoolId=self.user_pool_id,
            ClientId=application_client_id,
        )

    def create_user(
        self,
        params: CreateUserParams,
    ) -> None:
        parameters = {
            "UserPoolId": self.user_pool_id,
            "Username": params["username"],
            "UserAttributes": [
                {"Name": "preferred_username", "Value": params["preferred_username"]},
                {"Name": "custom:tenant_id", "Value": params["tenant_id"]},
                {"Name": "custom:user_id", "Value": params["user_id"]},
            ],
        }
        if params.get("email"):
            parameters["UserAttributes"].extend(
                [
                    {"Name": "email", "Value": params["email"]},
                    {"Name": "email_verified", "Value": "true"},
                ],
            )

        self.client.admin_create_user(**parameters)
        logger.debug(
            "Cognito user created.",
            username=params["username"],
            tenant_id=params["tenant_id"],
            user_id=params["user_id"],
        )

    def update_user(
        self,
        params: UpdateUserParams,
    ) -> None:
        parameters = {
            "UserPoolId": self.user_pool_id,
            "Username": params["username"],
            "UserAttributes": [
                {"Name": "preferred_username", "Value": params["preferred_username"]},
            ],
        }
        if params.get("email"):
            parameters["UserAttributes"].extend(
                [
                    {"Name": "email", "Value": params["email"]},
                    {"Name": "email_verified", "Value": "true"},
                ],
            )
        else:
            # NOTE: admin_update_user_attributes は、UserAttributes で指定した属性のみ更新するため、
            # 空にしたい属性は admin_delete_user_attributes で削除する
            self.client.admin_delete_user_attributes(
                UserPoolId=self.user_pool_id,
                Username=params["username"],
                UserAttributeNames=["email"],
            )

        self.client.admin_update_user_attributes(**parameters)
        logger.debug("Cognito user updated.", username=params["username"])

    def delete_user(self, username: str) -> None:
        self.client.admin_delete_user(
            UserPoolId=self.user_pool_id,
            Username=username,
        )
        logger.debug("Cognito user deleted.", username=username)


class StubCognitoIdpClient:
    """ローカル環境用の Cognito IDP スタブ。実際のAWS呼び出しを行わずダミー値を返す。"""

    def __init__(self, user_pool_id: str) -> None:
        self.user_pool_id = user_pool_id

    def create_user_pool_client(self, client_name: str, params: UserPoolClientParams) -> str:
        client_id = f"stub_{uuid.uuid4().hex[:20]}"
        logger.info(f"[STUB] create_user_pool_client: {client_name} -> {client_id}")
        return client_id

    def delete_user_pool_client(self, application_client_id: str) -> None:
        logger.info(f"[STUB] delete_user_pool_client: {application_client_id}")

    def create_user(self, params: CreateUserParams) -> None:
        logger.info(f"[STUB] create_user: {params['username']}")

    def update_user(self, params: UpdateUserParams) -> None:
        logger.info(f"[STUB] update_user: {params['username']}")

    def delete_user(self, username: str) -> None:
        logger.info(f"[STUB] delete_user: {username}")
