import datetime
from datetime import date
from decimal import Decimal
from enum import StrEnum
from uuid import UUID
from zoneinfo import ZoneInfo

from pydantic import BaseModel, Field, field_serializer, model_validator
from src.__types.index import Unit


class QuotaAllocationStatus(StrEnum):
    ACTIVE = "active"  # 利用可能
    EXCEEDED = "exceeded"  # 超過
    SCHEDULED = "scheduled"  # 有効開始日が未来 ※ 利用不可能
    EXPIRED = "expired"  # 有効終了日が過去


class QuotaAllocationType(StrEnum):
    """クォータ割り当ての契約種別

    COMPANY AIの契約には以下の種類が存在し、契約毎にクォータ割り当てが存在する。
    - 基本契約:
        利用にあたり必須となる契約。有効期限が来たら自動更新される。各テナントごとに一つのみ存在する。
    - 随時拡張契約:
        基本契約の上限を超えて、追加でAIユニットを購入する場合に締結される。任意の数だけ存在する。
    """

    BASE = "base"  # 基本契約
    TOP_UP = "top_up"  # 随時拡張契約

    @property
    def label(self) -> str:
        labels = {
            QuotaAllocationType.BASE: "基本契約",
            QuotaAllocationType.TOP_UP: "随時拡張",
        }
        return labels[self]


class QuotaAllocation(BaseModel):
    """クォータ割り当て

    クォータ割り当てとは、『COMPANY AI契約と1:1で対応する購入されたAIユニット量』を意味する。
    クォータ割り当ては、それぞれ総量、使用量、有効期間などの情報を持つ。
    使用量が総量に達した場合、ステータスがEXCEEDEDに変更され、それ以上の使用ができなくなる。
    テナントは複数のクォータ割り当てを持つことができる。

    AIユニット量は全てDecimal型で表現され、シリアライズ時に整数値として扱われる。 ※ __types#index#Unitを参照

    """

    id: UUID = Field(..., description="ID", exclude=True)
    type: QuotaAllocationType = Field(..., description="タイプ")
    status: QuotaAllocationStatus = Field(..., description="ステータス")
    # OpenAPIのschemaにてDecimal型はString型として扱われるため、json_schema_extraでintegerに変換
    amount: Unit = Field(..., json_schema_extra={"type": "integer"}, description="総量")
    used: Unit = Field(..., json_schema_extra={"type": "integer"}, description="使用量")
    valid_from_date: date = Field(..., description="有効開始日時 (YYYY-MM-DD形式)")
    valid_to_date: date = Field(..., description="有効終了日時 (YYYY-MM-DD形式)")

    @field_serializer("valid_from_date", "valid_to_date")
    def serialize_date(self, value: date) -> str:
        """CTMのUIのため、日付をYYYY-MM-DD形式の文字列に変換"""
        return value.strftime("%Y-%m-%d")

    @model_validator(mode="after")
    def validate_dates(self) -> "QuotaAllocation":
        """有効開始日と有効終了日の整合性を確認する"""
        if self.valid_from_date >= self.valid_to_date:
            raise ValueError("valid_from_date must be before valid_to_date")
        return self

    @model_validator(mode="after")
    def validate_used(self) -> "QuotaAllocation":
        """使用量の整合性を確認する"""
        if self.used < 0:
            raise ValueError("used must be a non-negative value")
        if self.used > self.amount:
            raise ValueError("used must not exceed amount")
        return self

    @model_validator(mode="after")
    def validate_scheduled_used(self) -> "QuotaAllocation":
        """SCHEDULEDステータスのクォータ割り当ての使用量の整合性を確認する"""
        if self.status == QuotaAllocationStatus.SCHEDULED and self.used > 0:
            raise ValueError("used must be 0 if status is SCHEDULED")
        return self

    @property
    def remaining_months(self) -> int:
        """有効期間の残り月数を計算する"""
        today = date.today()
        if self.valid_to_date < today:
            return 0
        return (self.valid_to_date.year - today.year) * 12 + (self.valid_to_date.month - today.month) + 1

    @classmethod
    def create(
        cls,
        id: UUID,
        type: QuotaAllocationType,
        amount: Unit,
        valid_from_date: date,
        valid_to_date: date,
    ) -> "QuotaAllocation":
        """契約情報からクォータ割り当てを生成するファクトリメソッド

        現在日付は Asia/Tokyo タイムゾーンを基準に取得する。
        """
        current_date = datetime.datetime.now(tz=ZoneInfo("Asia/Tokyo")).date()

        if current_date < valid_from_date:
            status = QuotaAllocationStatus.SCHEDULED
        elif current_date > valid_to_date:
            status = QuotaAllocationStatus.EXPIRED
        else:
            status = QuotaAllocationStatus.ACTIVE

        return cls(
            id=id,
            type=type,
            status=status,
            amount=amount,
            used=Decimal(0),
            valid_from_date=valid_from_date,
            valid_to_date=valid_to_date,
        )

    def use(self, unit: Unit) -> Unit:
        """クォータ割り当てからユニットを差し引き、余りを返す"""
        if self.status != QuotaAllocationStatus.ACTIVE:
            raise ValueError("Cannot use quota allocation that is not active")
        if unit <= 0:
            raise ValueError("Amount to use must be positive")

        remaining = self.amount - self.used
        if unit > remaining:
            # 状態更新
            self.used += remaining
            self.status = QuotaAllocationStatus.EXCEEDED
            return unit - remaining
        self.used += unit
        return Decimal(0)
