from datetime import datetime

from psycopg import Connection
from pydantic import BaseModel, SecretStr
from src.__types.errors import ClientError
from src.__types.index import Client
from src.domain.entities.client_api_key import ClientApiKey
from src.domain.services.client_api_key_service import ClientApiKeyService
from src.infrastructure.repositories.client_api_key_repository import ClientApiKeyRepository


class ApiKeyCredentials(BaseModel):
    key: SecretStr
    expires_at: datetime | None


class ClientApiKeyUseCase:
    def __init__(
        self,
        api_key_service: ClientApiKeyService,
        api_key_repository: ClientApiKeyRepository,
        connection: Connection,
    ) -> None:
        self.api_key_service = api_key_service
        self.api_key_repository = api_key_repository
        self.connection = connection

    @classmethod
    def create(cls, connection: Connection) -> "ClientApiKeyUseCase":
        return ClientApiKeyUseCase(
            api_key_service=ClientApiKeyService(),
            api_key_repository=ClientApiKeyRepository(connection=connection),
            connection=connection,
        )

    def rotate(
        self,
        client: Client,
        duration_days: int | None,
        current_key: ClientApiKey,
    ) -> ApiKeyCredentials:
        """APIキーをローテーションする。新規でキーを作成後、既存のキーを非推奨にする。

        Args:
            client (Client): クライアント情報
            duration_days (int|None): 新規で発行するAPIキーの有効期限(日数)。None の場合、無期限とする。
            current_key (ClientApiKey): ローテーションの対象となる現在のAPIキー。

        Returns:
            ApiKeyCredentials: 新規で発行されたAPIキー情報

        Raises:
            ClientError: 指定された現在のAPIキーが既にローテーション済みの場合

        """
        if current_key.rotated_at is not None:
            # すでにローテーションされたAPIキーを使用した再ローテーションは不可
            raise ClientError("This API key has been rotated. Use the new key.")

        issued_key = self.api_key_service.issue(client=client, duration_days=duration_days)
        rotated_key = current_key.mark_as_rotated()

        with self.connection.transaction():
            self.api_key_repository.add(client_api_key=issued_key.entity)
            self.api_key_repository.update_for_rotation(client_api_key=rotated_key)

        return ApiKeyCredentials(key=issued_key.plain_key, expires_at=issued_key.entity.expires_at)
