from decimal import Decimal
from uuid import UUID

from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, computed_field, field_serializer, model_validator
from src.__types.index import Unit
from src.domain.entities.quota_allocation import QuotaAllocation, QuotaAllocationStatus, QuotaAllocationType


class Quota(BaseModel):
    """クォータ

    クォータとは、『テナントが購入している全てのクォータ割り当ての集合』を意味する。
    テナントのAIユニット上限や使用量、残量などを計算を行うことができる。

    各数値の計算方法

    上限値や使用量、残量は、"有効な"クォータ割り当てからのみ合算する。
    期限切れや超過したクォータ割り当ては、クォータの計算に含まれない。

    使用優先度

    使用されたAIユニットは以下の優先度でクォータ割り当てから差し引く。
    1. 最も有効終了日が近い、有効な随時拡張契約のクォータ割り当て
    2. 最も有効終了日が近い、有効な基本契約のクォータ割り当て
    """

    allocations: list[QuotaAllocation] = Field(
        ...,
        # クォータ割り当てのリストを出力したい場合は、Quotaを直接シリアライズするのではなく一度取り出すこと
        exclude=True,
    )

    @computed_field(
        # OpenAPIのschemaにてDecimal型はString型として扱われるため、json_schema_extraでintegerに変換
        json_schema_extra={"type": "integer"},
        description="使用可能な上限",
    )
    @property
    def limit(self) -> Unit:
        """使用可能なAIユニットの総量"""
        return sum(
            (allocation.amount for allocation in self.allocations if allocation.status == QuotaAllocationStatus.ACTIVE),
            start=Decimal(0),
        )

    @computed_field(
        # OpenAPIのschemaにてDecimal型はString型として扱われるため、json_schema_extraでintegerに変換
        json_schema_extra={"type": "integer"},
        description="使用量",
    )
    @property
    def used(self) -> Unit:
        return sum(
            (allocation.used for allocation in self.allocations if allocation.status == QuotaAllocationStatus.ACTIVE),
            start=Decimal(0),
        )

    @computed_field(
        # OpenAPIのschemaにてDecimal型はString型として扱われるため、json_schema_extraでintegerに変換
        json_schema_extra={"type": "integer"},
        description="残量",
    )
    @property
    def remaining(self) -> Unit:
        return self.limit - self.used

    @field_serializer("remaining")
    def serialize_remaining(self, _: Unit) -> int:
        # limitとusedはシリアライズ時に整数に切り上げるため、切り上げた値でremainingを計算する
        adapter = TypeAdapter(Unit)  # PlanSerializerを起動するため
        return adapter.dump_python(self.limit) - adapter.dump_python(self.used)

    # TODO： 有効な基本契約を2つ以上持てるようにする場合に削除する
    @model_validator(mode="after")
    def validate_allocations(self) -> "Quota":
        """有効な基本契約のクォータ割り当てが1つだけ存在することを確認する"""
        active_base_allocations = [
            allocation
            for allocation in self.allocations
            if allocation.status == QuotaAllocationStatus.ACTIVE and allocation.type == QuotaAllocationType.BASE
        ]
        if len(active_base_allocations) > 1:
            raise ValueError("There should be at most one active base allocation.")

        return self

    @property
    def in_use_allocation_id(self) -> UUID | None:
        """現在使用中のクォータ割り当てのIDを返す"""
        active_allocations = [
            allocation for allocation in self.allocations if allocation.status == QuotaAllocationStatus.ACTIVE
        ]
        if active_allocations:
            sorted_allocations = sorted(
                active_allocations,
                key=lambda x: (x.valid_to_date, x.type != QuotaAllocationType.TOP_UP),
            )
            return sorted_allocations[0].id

        return None

    @property
    def is_exceeded(self) -> bool:
        """クォータが超過しているかどうかを返す"""
        return self.remaining <= Decimal(0)

    # TODO： 仕様確定後、有効な基本契約が2つ以上の場合を考慮してロジック修正する
    @property
    def remaining_per_remaining_month(self) -> Decimal:
        """有効な基本契約のクォータ割り当ての残り月数で残量を割った値を返す。消費ペースを計算するために使用される。"""
        for allocation in self.allocations:
            if allocation.type == QuotaAllocationType.BASE:
                return self.remaining / allocation.remaining_months
        return Decimal(0)

    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "limit": 100000,
                "used": 60000,
                "remaining": 40000,
            },
        },
    )
