import hashlib
import secrets
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

from pydantic import BaseModel, SecretStr
from src.__types.index import Client
from src.domain.entities.client_api_key import ClientApiKey, ClientApiKeyAlgorithm, ClientApiKeyStatus
from src.settings import Settings

settings = Settings()


class ClientApiKeyWithPlain(BaseModel):
    entity: ClientApiKey
    plain_key: SecretStr


class ClientApiKeyService:
    """クライアントAPIキーに関するサービス"""

    def __init__(self) -> None:
        if not settings.stage:
            raise ValueError("stage is required in settings")

        self.stage = settings.stage

    def issue(self, client: Client, duration_days: int | None) -> ClientApiKeyWithPlain:
        """クライアントAPIキーを発行する

        Args:
            client (Client): クライアント情報
            duration_days (int|None): APIキーの有効期限(日数)。None の場合、有効期限なしとする。

        Returns:
            ClientApiKeyWithPlain: 発行されたAPIキー情報(エンティティと平文のキー)

        """
        key_id = self._generate_key_id()
        raw_secret = self._generate_raw_secret()
        api_key = self._generate_api_key(client.name, client.stage, key_id, raw_secret)
        hashed_secret = self._generate_hashed_secret(raw_secret)
        now = datetime.now(tz=ZoneInfo("Asia/Tokyo"))

        entity = ClientApiKey(
            id=key_id,
            client_id=client.id,
            hashed_secret=hashed_secret,
            algorithm=ClientApiKeyAlgorithm.SHA256,
            status=ClientApiKeyStatus.ACTIVE,
            rotated_at=None,
            expires_at=now + timedelta(days=duration_days) if duration_days else None,
        )
        return ClientApiKeyWithPlain(
            entity=entity,
            plain_key=SecretStr(api_key),
        )

    def _generate_api_key(self, client_name: str, client_stage: str, key_id: str, raw_secret: str) -> str:
        return f"{self.stage}.{client_name}.{client_stage}.{key_id}.{raw_secret}"

    def _generate_key_id(self) -> str:
        return secrets.token_hex(12)

    def _generate_raw_secret(self) -> str:
        return secrets.token_urlsafe(32)

    def _generate_hashed_secret(self, raw_secret: str) -> str:
        return hashlib.sha256(raw_secret.encode()).hexdigest()
