from datetime import datetime
from decimal import Decimal
from enum import StrEnum
from typing import Annotated, Generic, TypeVar
from uuid import UUID
from zoneinfo import ZoneInfo

from pydantic import BaseModel, BeforeValidator, ConfigDict, EmailStr, Field, PlainSerializer, field_serializer
from src.const import DEFAULT_DECIMAL_ROUNDING

# 数値


Unit = Annotated[
    Decimal,
    Field(...),
    BeforeValidator(
        # 小数点以下12桁までの精度
        lambda x: x.quantize(Decimal("1e-12"), rounding=DEFAULT_DECIMAL_ROUNDING),
    ),
    PlainSerializer(
        # 小数以下を切り上げ
        lambda x: int(x.to_integral_value(rounding=DEFAULT_DECIMAL_ROUNDING)),
    ),
]


# Enum
class GroupBy(StrEnum):
    MONTH = "month"
    YEAR = "year"
    SERVICE = "service"


class Distribution(StrEnum):
    WHI_SFDC = "whi_sfdc"
    CYDAS_SFDC = "cydas_sfdc"


class ClientTenantStatus(StrEnum):
    ACTIVE = "active"
    PENDING = "pending"


# Generics

T = TypeVar("T")


class CountedList(BaseModel, Generic[T]):
    """総件数付きリスト"""

    total: int
    items: list[T]


# Data Transfer Objects


class Tenant(BaseModel):
    """AIユニット管理のテナント

    AIユニット管理上のテナント。
    SFDCの取引先IDと1:1で関係する。
    ※ 現状、WHIのSFDCの取引先IDとCYDASのSFDC取引先IDをどちらも保持する。

    管理画面における管理単位であり、かつ各クライアントのテナントを集約する単位となる。
    例えば、AIユニットのWHIテナントは、Bizmatch/MypageのWHIテナントとHCIのWHIテナントを集約する。
    """

    id: UUID
    m2m_application_client_id: str  # M2M認証のためのCognitoのアプリケーションクライアントID
    user_application_client_id: str | None = None  # ユーザー認証のためのCognitoのアプリケーションクライアントID
    legal_name: str
    name: str | None = None  # 管理画面上のテナント名
    whi_customer_id: list[str] | None
    cydas_customer_id: list[str] | None
    allowed_m2m_metadata: list[dict[str, str]]  # M2M認証のためのMetadataのリスト


class TenantAuthInfo(BaseModel):
    """ユーザー認証に必要なテナント情報"""

    tenant_id: UUID
    user_application_client_id: str


class UserWriteAttribute(BaseModel, frozen=True):
    """ユーザー属性の更新内容"""

    display_id: str = Field(..., description="ID", min_length=1)
    display_name: str = Field(..., description="名称", min_length=1)
    email: EmailStr = Field(..., description="メールアドレス", min_length=1)

    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "display_id": "U0001",
                "display_name": "Mock User 1",
                "email": "mock.user1@example.com",
            },
        },
    )


class User(BaseModel):
    """AIユニット管理のユーザー

    AIユニット管理上のユーザー。
    テナントに紐づく。
    """

    id: UUID = Field(..., description="内部ID")
    display_id: str = Field(..., description="ID")
    display_name: str = Field(..., description="表示名")
    email: EmailStr | None = Field(..., description="メールアドレス")


class Client(BaseModel):
    """生成AI利用データの送信元クライアント

    各サービスの環境（本番、ステージング等）をクライアントとして管理する。
    例えば。HCIの本番環境、開発環境など。

    各クライアントは登録されたサービスの使用イベントのみ送信可能で、
    他サービスのデータ送信は制限されている。
    """  # noqa: RUF002

    id: UUID
    name: str
    stage: str


class ClientTenant(BaseModel):
    """クライアント内のテナント情報

    各Client（HCI、Bizmatch/Mypageなど）が持つTenantを表す。
    Clientと1:Nの関係を持つ。
    全てのClientTenantは単一のTenantと紐づき、Quotaを共有する。
    同一のTenantに紐づくClientTenantがClient内に複数存在するケースを許容する。
    例えば、検証用環境など。
    """  # noqa: RUF002

    id: UUID
    tenant_id: UUID
    client_id: UUID | None = None
    name: str
    distribution: Distribution | None = None
    customer_id: str | None = None
    base_url: str | None = None


class PendingClientTenant(BaseModel):
    """Tenantに紐づけられていないClientTenant

    Tenantの発行前に、ClientがClientTenantを発行したケースに対応するためのEntity。
    Clientからの登録情報を保持し、SFDCの情報が一致するTenantが発行された際にはClientTenantに移行する。
    """

    # NOTE: ClientAPIからのみで作成されるため、client_id=nullのClientTenantは存在し得ない。
    client_id: UUID
    name: str
    distribution: Distribution
    customer_id: str
    base_url: str | None = None


class Price(BaseModel):
    """生成AIの利用方法ごとの価格情報

    生成AIの利用方法を『使用区分=code』として分類する。
    単価は『価格=price』として表現する。
    使用区分毎に単価の単位は異なる。
    """

    code: str
    price: Decimal


class QuotaUsage(BaseModel):
    """集計されたクォータ使用実績

    クォータ使用実績とは、『クォータ割り当てから差し引かれたAIユニットおよびそのメタデータ』を意味する。
    このモデルは、特定の月、サービス、またはそれらの組み合わせなど、特定の条件に一致するクォータ使用実績の集計結果を表現する。

    各オブジェクトはテーブルの列またはリスト要素に対応し、複数を組み合わせることで、様々な次元での使用実績分析を表現する。
    - 月次使用実績統計
    - サービス別使用実績統計
    - 月次・サービス別使用実績統計
    """

    used: Unit = Field(..., description="使用量")
    service: str | None = Field(default=None, description="サービス名 ※ group_byに含めた場合のみ")
    year: int | None = Field(default=None, description="年 ※ group_byに含めた場合のみ")
    month: int | None = Field(default=None, description="月 ※ group_byに含めた場合のみ")


class UsageEventRequestPayload(BaseModel):
    """使用イベントのリクエスト情報

    いつ、誰が、どのような使用イベントを送信したかの情報を保持する。
    """

    tenant: Tenant = Field(..., description="テナント情報")
    client: Client = Field(..., description="クライアント情報")
    client_tenant: ClientTenant = Field(..., description="クライアントのテナント情報")
    idempotency_key: UUID = Field(..., description="一意のリクエスト識別子")
    request_events: list[dict] = Field(..., description="リクエストされたイベントの内容")
    timestamp: datetime = Field(
        default_factory=lambda: datetime.now(ZoneInfo("Asia/Tokyo")),
        description="リクエストのタイムスタンプ",
    )

    @field_serializer("tenant", "client", "client_tenant")
    def serialize_model(self, value: BaseModel) -> dict:
        return value.model_dump(mode="json")

    @field_serializer("idempotency_key")
    def serialize_idempotency_key(self, value: UUID) -> str:
        return str(value)

    @field_serializer("timestamp")
    def serialize_timestamp(self, value: datetime) -> str:
        return value.strftime("%Y-%m-%d %H:%M:%S.%f")


class UsageEventRequestMessage(BaseModel):
    """使用イベントリクエストのメッセージ内容"""

    tenant: Tenant = Field(..., description="テナント情報")
    object_key: str = Field(..., description="使用イベントリクエストの保存先キー")


class ContractProvider(BaseModel):
    """契約情報のプロバイダー

    AIユニットの契約情報の提供元。
    連携元・商流に対して1つのContractProviderを発行する。
    ※ 2026-05 時点では、WHIのSFDC　・　CYDASのSFDCの2つを想定。
    """

    id: UUID
    application_client_id: str
    name: str
