from __future__ import annotations

from datetime import datetime
from decimal import Decimal
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator

PricingModel = Literal["subscription", "usage", "hybrid", "pilot", "custom"]
BillingInterval = Literal["monthly", "quarterly", "annual", "contract"]
SubscriptionStatus = Literal["trialing", "active", "past_due", "suspended", "canceled", "expired"]
EntitlementType = Literal["feature", "metered", "unlimited"]
EntitlementStatus = Literal["active", "suspended", "expired"]
ResetInterval = Literal["none", "monthly", "quarterly", "annual", "contract"]
OveragePolicy = Literal["block", "allow", "alert"]


class CommercialPlanCreate(BaseModel):
    plan_code: str = Field(min_length=1, max_length=80)
    name: str = Field(min_length=1, max_length=160)
    pricing_model: PricingModel
    billing_interval: BillingInterval
    currency: str = Field(pattern=r"^[A-Za-z]{3}$")
    base_fee: Decimal = Field(ge=0, max_digits=20, decimal_places=4)
    included_seats: int = Field(default=0, ge=0)
    overage_enabled: bool = False
    effective_from: datetime
    effective_to: datetime | None = None
    contract_terms_json: dict[str, Any] = Field(default_factory=dict)
    reason: str = Field(
        default="平台管理员创建套餐版本",
        min_length=2,
        max_length=500,
    )

    @field_validator("effective_from", "effective_to")
    @classmethod
    def require_timezone(cls, value: datetime | None) -> datetime | None:
        return _require_timezone(value)

    @model_validator(mode="after")
    def validate_window(self) -> CommercialPlanCreate:
        if self.effective_to is not None and self.effective_to <= self.effective_from:
            raise ValueError("套餐失效时间必须晚于生效时间。")
        return self


class CommercialPlanRead(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: str
    tenant_id: str
    plan_code: str
    name: str
    pricing_model: PricingModel
    billing_interval: BillingInterval
    currency: str
    base_fee: Decimal
    included_seats: int
    overage_enabled: bool
    status: Literal["draft", "active", "retired"]
    effective_from: datetime
    effective_to: datetime | None = None
    version: int
    contract_terms_json: dict[str, Any] = Field(default_factory=dict)
    created_by: str
    created_at: datetime
    updated_at: datetime


class CommercialPlanActivationRead(BaseModel):
    plan: CommercialPlanRead
    retired_plan_ids: list[str] = Field(default_factory=list)


class CommercialVersionAction(BaseModel):
    expected_version: int = Field(ge=1)
    reason: str = Field(min_length=2, max_length=500)


class CommercialSubscriptionTransition(BaseModel):
    expected_version: int = Field(ge=1)
    target_status: Literal["past_due", "suspended", "canceled", "expired"]
    reason: str = Field(min_length=2, max_length=500)


class CommercialSubscriptionCreate(BaseModel):
    subscription_key: str = Field(min_length=1, max_length=120)
    plan_id: str = Field(min_length=1, max_length=36)
    status: Literal["trialing", "active"] = "active"
    starts_at: datetime
    ends_at: datetime | None = None
    current_period_start: datetime
    current_period_end: datetime
    seats: int = Field(ge=1)
    auto_renew: bool = False
    external_provider: str | None = Field(default=None, max_length=60)
    external_subscription_id: str | None = Field(default=None, max_length=160)
    metadata_json: dict[str, Any] = Field(default_factory=dict)
    reason: str = Field(
        default="平台管理员创建订阅",
        min_length=2,
        max_length=500,
    )

    @field_validator(
        "starts_at",
        "ends_at",
        "current_period_start",
        "current_period_end",
    )
    @classmethod
    def require_timezone(cls, value: datetime | None) -> datetime | None:
        return _require_timezone(value)

    @model_validator(mode="after")
    def validate_windows_and_provider(self) -> CommercialSubscriptionCreate:
        if self.current_period_end <= self.current_period_start:
            raise ValueError("当前订阅周期结束时间必须晚于开始时间。")
        if self.ends_at is not None and self.ends_at <= self.starts_at:
            raise ValueError("订阅结束时间必须晚于开始时间。")
        if bool(self.external_provider) != bool(self.external_subscription_id):
            raise ValueError("外部订阅提供商和订阅编号必须同时填写或同时留空。")
        return self


class CommercialSubscriptionRead(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: str
    tenant_id: str
    subscription_key: str
    plan_id: str
    status: SubscriptionStatus
    starts_at: datetime
    ends_at: datetime | None = None
    current_period_start: datetime
    current_period_end: datetime
    seats: int
    base_fee_snapshot: Decimal
    currency: str
    billing_interval: BillingInterval
    auto_renew: bool
    external_provider: str | None = None
    external_subscription_id: str | None = None
    canceled_at: datetime | None = None
    version: int
    metadata_json: dict[str, Any] = Field(default_factory=dict)
    created_by: str
    created_at: datetime
    updated_at: datetime


class CommercialEntitlementUpsert(BaseModel):
    subscription_id: str = Field(min_length=1, max_length=36)
    entitlement_key: str = Field(min_length=1, max_length=120)
    metric_key: str = Field(min_length=1, max_length=120)
    entitlement_type: EntitlementType
    unit: str = Field(min_length=1, max_length=40)
    included_quantity: Decimal | None = Field(default=None, ge=0)
    hard_limit_quantity: Decimal | None = Field(default=None, ge=0)
    reset_interval: ResetInterval
    overage_policy: OveragePolicy
    status: EntitlementStatus = "active"
    effective_from: datetime
    effective_to: datetime | None = None
    config_json: dict[str, Any] = Field(default_factory=dict)
    reason: str = Field(
        default="平台管理员维护商业权益",
        min_length=2,
        max_length=500,
    )

    @field_validator("effective_from", "effective_to")
    @classmethod
    def require_timezone(cls, value: datetime | None) -> datetime | None:
        return _require_timezone(value)

    @model_validator(mode="after")
    def validate_shape(self) -> CommercialEntitlementUpsert:
        if self.effective_to is not None and self.effective_to <= self.effective_from:
            raise ValueError("权益失效时间必须晚于生效时间。")
        if self.entitlement_type == "unlimited":
            if self.included_quantity is not None or self.hard_limit_quantity is not None:
                raise ValueError("无限权益不能配置包含量或硬配额。")
        elif self.entitlement_type == "feature":
            if self.included_quantity not in {Decimal("0"), Decimal("1")}:
                raise ValueError("功能权益包含量只能是 0 或 1。")
            if self.hard_limit_quantity not in {None, Decimal("0"), Decimal("1")}:
                raise ValueError("功能权益硬配额只能是 0 或 1。")
        else:
            if self.included_quantity is None:
                raise ValueError("计量权益必须配置包含量。")
            if (
                self.hard_limit_quantity is not None
                and self.hard_limit_quantity < self.included_quantity
            ):
                raise ValueError("硬配额不能小于包含量。")
        return self


class CommercialEntitlementRead(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: str
    tenant_id: str
    subscription_id: str
    entitlement_key: str
    metric_key: str
    entitlement_type: EntitlementType
    unit: str
    included_quantity: Decimal | None = None
    hard_limit_quantity: Decimal | None = None
    reset_interval: ResetInterval
    overage_policy: OveragePolicy
    status: EntitlementStatus
    effective_from: datetime
    effective_to: datetime | None = None
    version: int
    config_json: dict[str, Any] = Field(default_factory=dict)
    created_at: datetime
    updated_at: datetime


class UsageMeterEventCreate(BaseModel):
    subscription_id: str = Field(min_length=1, max_length=36)
    entitlement_id: str = Field(min_length=1, max_length=36)
    event_type: Literal["usage", "credit", "adjustment", "reversal"] = "usage"
    quantity: Decimal = Field(max_digits=20, decimal_places=6)
    occurred_at: datetime
    source_system: str = Field(min_length=1, max_length=80)
    idempotency_key: str = Field(min_length=1, max_length=160)
    reversal_of_event_id: str | None = Field(default=None, max_length=36)
    subject_type: str | None = Field(default=None, max_length=60)
    subject_id: str | None = Field(default=None, max_length=160)
    correlation_id: str | None = Field(default=None, max_length=120)
    trace_id: str | None = Field(default=None, max_length=120)
    metadata_json: dict[str, Any] = Field(default_factory=dict)

    @field_validator("occurred_at")
    @classmethod
    def require_timezone(cls, value: datetime) -> datetime:
        return _require_timezone(value)

    @model_validator(mode="after")
    def validate_usage_event(self) -> UsageMeterEventCreate:
        if self.event_type == "usage" and self.quantity <= 0:
            raise ValueError("usage 事件数量必须大于 0。")
        if self.event_type == "credit" and self.quantity >= 0:
            raise ValueError("credit 事件数量必须小于 0。")
        if self.event_type in {"adjustment", "reversal"} and self.quantity == 0:
            raise ValueError("adjustment/reversal 事件数量不能为 0。")
        if (self.event_type == "reversal") != bool(self.reversal_of_event_id):
            raise ValueError("只有 reversal 事件必须且只能填写被冲回事件。")
        if bool(self.subject_type) != bool(self.subject_id):
            raise ValueError("计量主体类型和编号必须同时填写或同时留空。")
        return self


class UsageMeterEventRead(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: str
    tenant_id: str
    subscription_id: str
    entitlement_id: str
    billing_period_id: str
    event_type: Literal["usage", "credit", "adjustment", "reversal"]
    metric_key: str
    quantity: Decimal
    unit: str
    period_key: str
    quota_period_key: str
    occurred_at: datetime
    source_system: str
    idempotency_key: str
    request_fingerprint: str
    reversal_of_event_id: str | None = None
    subject_type: str | None = None
    subject_id: str | None = None
    actor_type: Literal["system", "user", "integration", "admin"]
    actor_id: str
    correlation_id: str | None = None
    trace_id: str | None = None
    metadata_json: dict[str, Any] = Field(default_factory=dict)
    recorded_at: datetime


class CommercialCostEventCreate(BaseModel):
    subscription_id: str | None = Field(default=None, max_length=36)
    usage_event_id: str | None = Field(default=None, max_length=36)
    event_type: Literal["incurred", "credit", "adjustment", "reversal"] = "incurred"
    cost_category: Literal[
        "ai_inference",
        "ocr",
        "storage",
        "connector",
        "support",
        "implementation",
        "infrastructure",
        "payment",
        "other",
    ]
    quantity: Decimal = Field(gt=0, max_digits=20, decimal_places=6)
    unit: str = Field(min_length=1, max_length=40)
    unit_cost: Decimal = Field(ge=0, max_digits=20, decimal_places=8)
    original_currency: str = Field(pattern=r"^[A-Za-z]{3}$")
    reporting_currency: str = Field(pattern=r"^[A-Za-z]{3}$")
    fx_rate: Decimal = Field(gt=0, max_digits=20, decimal_places=8)
    provider: str | None = Field(default=None, max_length=120)
    sku: str | None = Field(default=None, max_length=120)
    model_name: str | None = Field(default=None, max_length=120)
    allocation_key: str = Field(min_length=1, max_length=160)
    occurred_at: datetime
    source_system: str = Field(min_length=1, max_length=80)
    idempotency_key: str = Field(min_length=1, max_length=160)
    reversal_of_cost_event_id: str | None = Field(default=None, max_length=36)
    correlation_id: str | None = Field(default=None, max_length=120)
    trace_id: str | None = Field(default=None, max_length=120)
    metadata_json: dict[str, Any] = Field(default_factory=dict)

    @field_validator("occurred_at")
    @classmethod
    def require_timezone(cls, value: datetime) -> datetime:
        return _require_timezone(value)

    @model_validator(mode="after")
    def validate_cost_event(self) -> CommercialCostEventCreate:
        if (self.event_type == "reversal") != bool(self.reversal_of_cost_event_id):
            raise ValueError("只有 reversal 成本事件必须且只能填写被冲回事件。")
        if self.usage_event_id and not self.subscription_id:
            raise ValueError("关联用量事件时必须填写订阅编号。")
        return self


class CommercialCostEventRead(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: str
    tenant_id: str
    subscription_id: str | None = None
    billing_period_id: str | None = None
    usage_event_id: str | None = None
    event_type: Literal["incurred", "credit", "adjustment", "reversal"]
    cost_category: str
    quantity: Decimal
    unit: str
    unit_cost: Decimal
    cost_amount: Decimal
    original_currency: str
    reporting_amount: Decimal
    reporting_currency: str
    fx_rate: Decimal
    provider: str | None = None
    sku: str | None = None
    model_name: str | None = None
    allocation_key: str
    occurred_at: datetime
    source_system: str
    idempotency_key: str
    request_fingerprint: str
    reversal_of_cost_event_id: str | None = None
    correlation_id: str | None = None
    trace_id: str | None = None
    metadata_json: dict[str, Any] = Field(default_factory=dict)
    recorded_at: datetime


class CommercialMutationRead(BaseModel):
    created: bool
    usage_event: UsageMeterEventRead | None = None
    cost_event: CommercialCostEventRead | None = None


class CommercialQuotaRead(BaseModel):
    entitlement: CommercialEntitlementRead
    billing_period_id: str
    period_key: str
    quota_period_key: str
    used_quantity: Decimal
    reserved_quantity: Decimal = Decimal("0")
    included_remaining: Decimal | None = None
    hard_limit_remaining: Decimal | None = None
    overage_quantity: Decimal
    status: Literal["available", "approaching", "exhausted", "unlimited", "inactive"]
    commercially_allowed: bool
    reason: str


class CommercialAccountRead(BaseModel):
    tenant_id: str
    as_of: datetime
    data_status: Literal["available", "partial", "unavailable"]
    plan: CommercialPlanRead | None = None
    subscription: CommercialSubscriptionRead | None = None
    quotas: list[CommercialQuotaRead] = Field(default_factory=list)
    notes: list[str] = Field(default_factory=list)


class EntitlementGateRead(BaseModel):
    tenant_id: str
    entitlement_key: str
    requested_quantity: Decimal
    commercial_allowed: bool
    security_decision: Literal["allow", "deny", "human_review"]
    final_allowed: bool
    reason: str
    quota: CommercialQuotaRead | None = None


class CommercialMoneyRead(BaseModel):
    currency: str
    amount: Decimal
    basis: str


class CommercialRatioRead(BaseModel):
    currency: str
    ratio: Decimal
    numerator: Decimal
    denominator: Decimal


class CommercialMetricRead(BaseModel):
    key: str
    label: str
    status: Literal["available", "partial", "unavailable"]
    values: list[CommercialMoneyRead] = Field(default_factory=list)
    ratios: list[CommercialRatioRead] = Field(default_factory=list)
    reason: str
    required_inputs: list[str] = Field(default_factory=list)
    notes: list[str] = Field(default_factory=list)


class CommercialAnalyticsRead(BaseModel):
    tenant_id: str
    start: datetime
    end: datetime
    as_of: datetime
    generated_at: datetime
    customer_charges: CommercialMetricRead
    internal_costs: CommercialMetricRead
    contribution_margin: CommercialMetricRead
    verified_cash_savings: CommercialMetricRead
    customer_roi: CommercialMetricRead
    customer_labor_value: CommercialMetricRead
    data_quality_status: Literal["complete", "partial", "unavailable"]
    data_quality_issues: list[str] = Field(default_factory=list)


class CommercialPricingScenarioWrite(BaseModel):
    start: datetime
    end: datetime
    as_of: datetime
    target_contribution_margin_rate: Decimal = Field(
        default=Decimal("0.65"),
        ge=0,
        lt=Decimal("0.95"),
        decimal_places=6,
    )
    max_verified_savings_share: Decimal = Field(
        default=Decimal("0.25"),
        gt=0,
        le=1,
        decimal_places=6,
    )

    @field_validator("start", "end", "as_of")
    @classmethod
    def require_timezone(cls, value: datetime) -> datetime:
        return _require_timezone(value)

    @model_validator(mode="after")
    def validate_window(self) -> CommercialPricingScenarioWrite:
        if self.end <= self.start:
            raise ValueError("定价分析结束时间必须晚于开始时间。")
        if self.as_of < self.start:
            raise ValueError("定价分析 as_of 不能早于分析开始时间。")
        return self


class CommercialPricingCurrencyScenarioRead(BaseModel):
    currency: str
    status: Literal["feasible", "insufficient_value", "cost_only", "unavailable"]
    internal_cost: Decimal | None = None
    verified_cash_savings: Decimal | None = None
    minimum_sustainable_charge: Decimal | None = None
    maximum_value_aligned_charge: Decimal | None = None
    maximum_success_fee: Decimal | None = None
    customer_roi_at_minimum_charge: Decimal | None = None
    contribution_margin_at_value_ceiling: Decimal | None = None
    reason: str


class CommercialPricingScenarioRead(BaseModel):
    tenant_id: str
    start: datetime
    end: datetime
    as_of: datetime
    target_contribution_margin_rate: Decimal
    max_verified_savings_share: Decimal
    recommended_model: Literal[
        "hybrid",
        "subscription",
        "pilot_collecting",
        "optimize_unit_economics",
    ]
    scenarios: list[CommercialPricingCurrencyScenarioRead] = Field(default_factory=list)
    evidence_status: Literal["complete", "partial", "unavailable"]
    notes: list[str] = Field(default_factory=list)


def _require_timezone(value: datetime | None) -> datetime | None:
    if value is not None and (value.tzinfo is None or value.utcoffset() is None):
        raise ValueError("商业事实时间必须显式包含时区。")
    return value
