from __future__ import annotations

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

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

SavingsValueType = Literal["cash", "labor"]
SavingsOpportunityStatus = Literal[
    "identified",
    "accepted",
    "in_progress",
    "realized",
    "verified",
    "reversed",
    "rejected",
    "expired",
]
SavingsOpportunityAction = Literal["accept", "start", "reject", "expire"]
SavingsOpportunityAvailableAction = Literal[
    "accept",
    "start",
    "reject",
    "expire",
    "record_realization",
]
SavingsRealizationAction = Literal["confirm", "reject", "reverse"]


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

    id: str
    tenant_id: str
    baseline_key: str
    baseline_type: Literal["historical_cohort", "policy_counterfactual", "manual"]
    dimension_type: str
    dimension_id: str
    metric_key: str
    unit: str
    original_currency: str | None = None
    baseline_value: Decimal
    window_start: datetime | None = None
    window_end: datetime | None = None
    sample_count: int
    method: str
    query_fingerprint: str
    data_quality_status: Literal["complete", "partial", "insufficient", "invalid"]
    data_quality_score: Decimal
    quality_issues_json: list[dict[str, Any]] = Field(default_factory=list)
    algorithm_version: str
    policy_version: str | None = None
    policy_effective_from: date | None = None
    policy_effective_to: date | None = None
    target_resource_type: str | None = None
    target_resource_id: str | None = None
    frozen_at: datetime
    frozen_by: str
    valid_until: datetime | None = None
    version: int
    created_at: datetime


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

    id: str
    tenant_id: str
    evidence_key: str
    entity_type: Literal["baseline", "opportunity", "realization"]
    entity_id: str
    evidence_role: str
    resource_type: str
    resource_id: str
    source_system: str
    external_event_id: str | None = None
    content_hash: str
    occurred_at: datetime
    collected_at: datetime
    verification_status: Literal["unverified", "verified", "rejected", "unavailable"]
    verified_by: str | None = None
    verified_at: datetime | None = None
    metadata_json: dict[str, Any] = Field(default_factory=dict)
    created_at: datetime


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

    id: str
    tenant_id: str
    aggregate_type: Literal["baseline", "opportunity", "realization"]
    aggregate_id: str
    action: str
    actor_id: str
    actor_name: str
    actor_type: Literal["user", "system", "agent", "service"]
    request_id: str
    expected_version: int
    result_version: int
    payload_fingerprint: str
    payload_json: dict[str, Any] = Field(default_factory=dict)
    before_json: dict[str, Any] = Field(default_factory=dict)
    after_json: dict[str, Any] = Field(default_factory=dict)
    response_json: dict[str, Any] = Field(default_factory=dict)
    correlation_id: str | None = None
    causation_id: str | None = None
    occurred_at: datetime


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

    id: str
    tenant_id: str
    realization_key: str
    opportunity_id: str
    expense_case_id: str
    claim_id: str
    claim_item_id: str | None = None
    business_event_id: str | None = None
    realization_type: Literal["actual", "reversal"]
    reversal_of_realization_id: str | None = None
    realized_at: datetime
    recorded_by_id: str
    recorded_by_name: str
    actual_gross: Decimal
    incremental_cost: Decimal
    actual_net: Decimal
    original_currency: str
    reporting_amount: Decimal
    reporting_currency: str
    fx_rate: Decimal
    fx_source: str
    fx_date: date
    fx_version: str
    attribution_method: str
    attribution_ratio: Decimal
    benefit_key: str
    dedupe_status: Literal["pending_review", "canonical", "duplicate", "excluded"]
    canonical_realization_id: str | None = None
    status: Literal["pending_confirmation", "finance_confirmed", "rejected", "reversed"]
    finance_confirmer_id: str | None = None
    finance_confirmer_name: str | None = None
    confirmed_at: datetime | None = None
    confirmation_note: str | None = None
    rejected_by_id: str | None = None
    rejected_by_name: str | None = None
    rejected_at: datetime | None = None
    rejection_reason: str | None = None
    reversed_by_id: str | None = None
    reversed_by_name: str | None = None
    reversed_at: datetime | None = None
    reversal_reason: str | None = None
    baseline_snapshot_json: dict[str, Any] = Field(default_factory=dict)
    final_snapshot_json: dict[str, Any] = Field(default_factory=dict)
    evidence_json: list[dict[str, Any]] = Field(default_factory=list)
    version: int
    created_at: datetime
    updated_at: datetime
    available_actions: list[SavingsRealizationAction] = Field(default_factory=list)
    read_only_reason: str = ""


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

    id: str
    tenant_id: str
    opportunity_key: str
    expense_case_id: str
    claim_id: str
    claim_item_id: str | None = None
    discovery_business_event_id: str | None = None
    source_type: str
    source_id: str
    category: str
    claim_no_snapshot: str
    title: str
    description: str
    value_kind: SavingsValueType
    exposure_amount: Decimal
    baseline_snapshot_id: str
    baseline_amount: Decimal
    target_amount: Decimal
    estimated_gross: Decimal
    estimated_cost: Decimal
    estimated_net: Decimal
    estimated_low: Decimal
    estimated_high: Decimal
    confidence: Decimal
    currency: str
    reporting_currency: str
    attribution_method: str
    ai_decision_id: str | None = None
    owner_id: str
    owner_name: str
    owner_role: str
    due_at: datetime | None = None
    status: SavingsOpportunityStatus
    version: int
    benefit_key: str
    suggested_action: str
    dimension_json: dict[str, Any] = Field(default_factory=dict)
    baseline_snapshot_json: dict[str, Any] = Field(default_factory=dict)
    evidence_json: list[dict[str, Any]] = Field(default_factory=list)
    accepted_at: datetime | None = None
    started_at: datetime | None = None
    realized_at: datetime | None = None
    verified_at: datetime | None = None
    closed_at: datetime | None = None
    created_at: datetime
    updated_at: datetime
    available_actions: list[SavingsOpportunityAvailableAction] = Field(default_factory=list)
    read_only_reason: str = ""
    baseline: ProfileBaselineSnapshotRead | None = None
    realizations: list[SavingsRealizationRead] = Field(default_factory=list)
    evidence: list[SavingsEvidenceLinkRead] = Field(default_factory=list)
    events: list[SavingsEventRead] = Field(default_factory=list)


class SavingsOpportunityListRead(BaseModel):
    items: list[SavingsOpportunityRead] = Field(default_factory=list)
    total: int = 0
    page: int = 1
    page_size: int = 20
    total_pages: int = 0
    generated_at: datetime


class SavingsActionBase(BaseModel):
    request_id: str = Field(min_length=8, max_length=120)
    expected_version: int = Field(ge=1)
    comment: str = Field(min_length=2, max_length=1000)

    @field_validator("request_id", "comment", mode="before")
    @classmethod
    def normalize_text(cls, value: Any) -> str:
        return str(value or "").strip()


class SavingsOpportunityActionCreate(SavingsActionBase):
    action: SavingsOpportunityAction


class SavingsEvidenceCreate(BaseModel):
    evidence_key: str = Field(min_length=1, max_length=160)
    evidence_role: str = Field(min_length=1, max_length=50)
    resource_type: str = Field(min_length=1, max_length=50)
    resource_id: str = Field(min_length=1, max_length=160)
    source_system: str = Field(min_length=1, max_length=60)
    external_event_id: str | None = Field(default=None, max_length=160)
    content_hash: str = Field(min_length=16, max_length=80)
    occurred_at: datetime
    verification_status: Literal["unverified", "unavailable"] = "unverified"
    metadata_json: dict[str, Any] = Field(default_factory=dict)

    @field_validator(
        "evidence_key",
        "evidence_role",
        "resource_type",
        "resource_id",
        "source_system",
        "external_event_id",
        "content_hash",
        mode="before",
    )
    @classmethod
    def normalize_optional_text(cls, value: Any) -> Any:
        if value is None:
            return None
        normalized = str(value).strip()
        return normalized or None


class SavingsRealizationCreate(SavingsActionBase):
    actual_gross: Decimal = Field(gt=0, max_digits=16, decimal_places=2)
    incremental_cost: Decimal = Field(
        default=Decimal("0.00"),
        ge=0,
        max_digits=16,
        decimal_places=2,
    )
    currency: str = Field(default="CNY", min_length=3, max_length=10)
    realized_at: datetime
    attribution_method: str = Field(min_length=2, max_length=80)
    attribution_ratio: Decimal = Field(default=Decimal("1.0000"), gt=0, le=1)
    evidence_level: Literal["business_state", "external_document", "external_verified"]
    evidence: list[SavingsEvidenceCreate] = Field(default_factory=list, max_length=30)

    @field_validator("currency", mode="before")
    @classmethod
    def normalize_currency(cls, value: Any) -> str:
        return str(value or "CNY").strip().upper()

    @field_validator("attribution_method", mode="before")
    @classmethod
    def normalize_method(cls, value: Any) -> str:
        return str(value or "").strip()

    @model_validator(mode="after")
    def require_result_evidence(self) -> SavingsRealizationCreate:
        if not self.evidence:
            raise ValueError("实际节省结果必须至少提供一条可追溯证据。")
        if self.incremental_cost > self.actual_gross:
            raise ValueError("执行成本不能高于实际毛节省。")
        return self


class SavingsRealizationActionCreate(SavingsActionBase):
    action: SavingsRealizationAction
    reversal_amount: Decimal | None = Field(default=None, gt=0, max_digits=16, decimal_places=2)
    evidence: list[SavingsEvidenceCreate] = Field(default_factory=list, max_length=30)

    @model_validator(mode="after")
    def validate_action_payload(self) -> SavingsRealizationActionCreate:
        if self.action == "reverse" and self.reversal_amount is None:
            raise ValueError("冲回动作必须提供冲回金额。")
        if self.action != "reverse" and self.reversal_amount is not None:
            raise ValueError("只有冲回动作可以提供冲回金额。")
        return self


class SavingsOpportunityMutationRead(BaseModel):
    opportunity: SavingsOpportunityRead
    event: SavingsEventRead
    replayed: bool = False


class SavingsRealizationMutationRead(BaseModel):
    realization: SavingsRealizationRead
    opportunity: SavingsOpportunityRead
    event: SavingsEventRead
    replayed: bool = False
