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

from app.services.expense_claim_budget_risk_flags import dedupe_budget_risk_flags


class ReimbursementCreate(BaseModel):
    request_no: str
    employee_id: str
    title: str
    category: str
    amount: Decimal
    reason: str | None = None


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

    id: str
    request_no: str
    employee_id: str
    title: str
    category: str
    status: str
    amount: Decimal
    reason: str | None
    created_at: datetime
    updated_at: datetime


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

    id: str
    item_date: date
    item_type: str
    item_reason: str
    item_location: str
    item_note: str = ""
    item_amount: Decimal
    invoice_id: str | None
    is_system_generated: bool = False
    created_at: datetime
    updated_at: datetime


class ExpenseClaimAttachmentAnalysisRead(BaseModel):
    severity: str
    label: str
    headline: str
    summary: str
    points: list[str] = Field(default_factory=list)
    rule_basis: list[str] = Field(default_factory=list)
    suggestion: str = ""


class ExpenseClaimAttachmentDocumentFieldRead(BaseModel):
    key: str
    label: str
    value: str


class ExpenseClaimAttachmentDocumentInfoRead(BaseModel):
    document_type: str = "other"
    document_type_label: str = "其他单据"
    scene_code: str = "other"
    scene_label: str = "其他票据"
    fields: list[ExpenseClaimAttachmentDocumentFieldRead] = Field(default_factory=list)


class ExpenseClaimAttachmentRequirementRead(BaseModel):
    matches: bool = False
    current_expense_type: str = "other"
    current_expense_type_label: str = "其他"
    allowed_scene_labels: list[str] = Field(default_factory=list)
    recognized_scene_code: str = "other"
    recognized_scene_label: str = "其他票据"
    recognized_document_type: str = "other"
    recognized_document_type_label: str = "其他单据"
    message: str = ""


class ExpenseClaimAttachmentRead(BaseModel):
    file_name: str
    storage_key: str
    media_type: str
    size_bytes: int
    uploaded_at: datetime | None = None
    previewable: bool = True
    preview_kind: str = ""
    preview_url: str = ""
    analysis: ExpenseClaimAttachmentAnalysisRead | None = None
    document_info: ExpenseClaimAttachmentDocumentInfoRead | None = None
    requirement_check: ExpenseClaimAttachmentRequirementRead | None = None


class ExpenseClaimItemUpdate(BaseModel):
    item_date: date | None = None
    item_type: str | None = None
    item_reason: str | None = None
    item_location: str | None = None
    item_note: str | None = None
    item_amount: Decimal | None = None
    invoice_id: str | None = None


class ExpenseClaimItemCreate(BaseModel):
    item_date: date | None = None
    item_type: str | None = None
    item_reason: str | None = None
    item_location: str | None = None
    item_note: str | None = None
    item_amount: Decimal | None = None
    invoice_id: str | None = None


class ExpenseClaimUpdate(BaseModel):
    reason: str | None = Field(default=None, max_length=500)


class ExpenseClaimStandardAdjustmentRisk(BaseModel):
    risk_id: str | None = Field(default=None, max_length=120)
    item_id: str | None = Field(default=None, max_length=120)
    title: str | None = Field(default=None, max_length=120)
    risk: str | None = Field(default=None, max_length=500)
    application_days: int | None = Field(
        default=None,
        ge=1,
        le=365,
        description="旧客户端展示提示；服务端按单据、明细及关联申请重新确定政策天数。",
    )
    original_amount: Decimal | None = Field(
        default=None,
        description="旧客户端展示提示；服务端仅使用数据库中的明细原金额。",
    )
    reimbursable_amount: Decimal | None = Field(
        default=None,
        description="旧客户端展示提示；服务端仅使用规则中心计算的可报销金额。",
    )


class ExpenseClaimStandardAdjustmentPayload(BaseModel):
    request_id: str | None = Field(
        default=None,
        min_length=1,
        max_length=120,
        description="客户端生成的幂等请求号；相同请求号只能用于同一组明细。",
    )
    expected_updated_at: datetime | None = Field(
        default=None,
        description="客户端最后读取到的单据更新时间，用于拒绝过期页面提交。",
    )
    risks: list[ExpenseClaimStandardAdjustmentRisk] = Field(default_factory=list, max_length=20)

    @field_validator("request_id")
    @classmethod
    def normalize_standard_adjustment_request_id(cls, value: str | None) -> str | None:
        normalized = str(value or "").strip()
        return normalized or None


class ExpenseClaimPreReviewRemediationRead(BaseModel):
    action: str
    target_item_ids: list[str] = Field(default_factory=list)
    required_fields: list[str] = Field(default_factory=list)
    alternative_action: str | None = None


class ExpenseClaimPreReviewFindingRead(BaseModel):
    risk_id: str
    rule_code: str = ""
    rule_version: str = ""
    severity: str
    disposition: Literal["fix", "review"]
    resolution_status: Literal["unresolved", "resolved"]
    actionability: str = ""
    source: str = "pre_review_finding"
    business_stage: str = ""
    risk_domain: str = ""
    visibility_scope: str = ""
    item_ids: list[str] = Field(default_factory=list)
    message: str
    remediation: ExpenseClaimPreReviewRemediationRead


class ExpenseClaimHistoricalCaseEvidenceRead(BaseModel):
    label: Literal["confirmed", "false_positive"]
    label_text: str
    advisory_only: Literal[True] = True
    score: float = 0.0
    scene_code: str = ""
    policy_ref: str = ""
    rule_version: str = ""
    version_status: Literal["matched", "stale"] = "matched"
    stale: bool = False
    summary: str


class ExpenseClaimPreReviewRead(BaseModel):
    review_id: str
    input_fingerprint: str
    rule_set_fingerprint: str
    review_context_fingerprint: str = ""
    pipeline_version: str
    reviewed_at: datetime
    decision: Literal["ready", "needs_fix", "ready_with_review"]
    passed: bool
    blocking_count: int = 0
    message: str
    findings: list[ExpenseClaimPreReviewFindingRead] = Field(default_factory=list)
    historical_case_evidence: list[ExpenseClaimHistoricalCaseEvidenceRead] = Field(
        default_factory=list
    )


class ExpenseClaimSubmitPayload(BaseModel):
    pre_review_id: str | None = Field(default=None, max_length=36)
    input_fingerprint: str | None = Field(default=None, max_length=80)


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

    id: str
    claim_no: str
    employee_id: str | None
    employee_name: str
    employee_no: str | None = None
    employee_email: str | None = None
    department_id: str | None
    department_name: str
    employee_position: str | None = None
    employee_grade: str | None = None
    manager_name: str | None = None
    finance_owner_name: str | None = None
    finance_approver_name: str | None = None
    budget_approver_name: str | None = None
    budget_approver_grade: str | None = None
    budget_approver_role_code: str | None = None
    role_labels: list[str] = Field(default_factory=list)
    project_code: str | None
    expense_type: str
    reason: str
    location: str
    amount: Decimal
    currency: str
    invoice_count: int
    occurred_at: datetime
    submitted_at: datetime | None
    status: str
    approval_stage: str | None
    risk_flags_json: list[Any] = Field(default_factory=list)
    created_at: datetime
    updated_at: datetime
    items: list[ExpenseClaimItemRead] = Field(default_factory=list)
    pre_review: ExpenseClaimPreReviewRead | None = None

    @field_validator("risk_flags_json", mode="before")
    @classmethod
    def dedupe_budget_risk_flags_for_read(cls, value: Any) -> list[Any]:
        if isinstance(value, list):
            return dedupe_budget_risk_flags(value)
        return []


class ExpenseClaimActionResponse(BaseModel):
    message: str
    claim_id: str
    status: str | None = None


class ExpenseApplicationPreviewActionPayload(BaseModel):
    source: str = Field(default="user_message", max_length=80)
    user_id: str | None = Field(default=None, max_length=120)
    conversation_id: str | None = Field(default=None, max_length=120)
    action_type: str | None = Field(default=None, pattern="^(save_draft|submit)$")
    decision_id: str | None = Field(default=None, max_length=36)
    request_id: str | None = Field(default=None, max_length=120)
    message: str = Field(min_length=1, max_length=4000)
    context_json: dict[str, Any] = Field(default_factory=dict)


class ExpenseApplicationPreviewActionResult(BaseModel):
    message: str
    answer: str
    suggested_actions: list[dict[str, Any]] = Field(default_factory=list)
    risk_flags: list[str] = Field(default_factory=list)
    requires_confirmation: bool = False
    draft_payload: dict[str, Any] | None = None
    decision_id: str | None = None
    decision_expires_at: datetime | None = None
    learning_receipts: list[dict[str, Any]] = Field(default_factory=list)


class ExpenseApplicationPreviewActionResponse(BaseModel):
    status: str = "succeeded"
    conversation_id: str | None = None
    result: ExpenseApplicationPreviewActionResult


class ExpenseClaimActionProtocolPayload(BaseModel):
    request_id: str = Field(min_length=1, max_length=120)
    expected_status: str = Field(min_length=1, max_length=30)
    expected_approval_stage: str = Field(min_length=1, max_length=50)

    @field_validator("request_id", "expected_status", "expected_approval_stage")
    @classmethod
    def validate_action_protocol_text(cls, value: str) -> str:
        normalized = str(value or "").strip()
        if not normalized:
            raise ValueError("审批动作协议字段不能为空。")
        return normalized

    @field_validator("expected_status")
    @classmethod
    def normalize_expected_status(cls, value: str) -> str:
        return value.lower()


class ExpenseClaimReturnPayload(ExpenseClaimActionProtocolPayload):
    reason: str | None = Field(default=None, max_length=500)
    reason_codes: list[str] = Field(default_factory=list, max_length=10)
    task_id: str | None = Field(default=None, min_length=1, max_length=36)
    expected_task_version: int | None = Field(default=None, ge=1)

    @model_validator(mode="after")
    def validate_task_precondition(self) -> ExpenseClaimReturnPayload:
        if (self.task_id is None) != (self.expected_task_version is None):
            raise ValueError("task_id 与 expected_task_version 必须同时提供。")
        return self


class ExpenseClaimApprovalPayload(ExpenseClaimActionProtocolPayload):
    opinion: str | None = Field(default=None, max_length=500)
    task_id: str | None = Field(default=None, min_length=1, max_length=36)
    expected_task_version: int | None = Field(default=None, ge=1)

    @model_validator(mode="after")
    def validate_task_precondition(self) -> ExpenseClaimApprovalPayload:
        if (self.task_id is None) != (self.expected_task_version is None):
            raise ValueError("task_id 与 expected_task_version 必须同时提供。")
        return self


class ExpenseClaimPaymentPayload(ExpenseClaimActionProtocolPayload):
    pass


class TravelReimbursementCalculatorRequest(BaseModel):
    days: int = Field(ge=1, le=365)
    location: str = Field(min_length=1, max_length=120)
    grade: str | None = Field(default=None, max_length=30)
    transport_mode: str | None = Field(default=None, max_length=30)
    origin_location: str | None = Field(default=None, max_length=120)
    travel_date: date | None = None


class TravelReimbursementCalculatorResponse(BaseModel):
    days: int
    location: str
    matched_city: str
    city_tier: str
    grade: str
    grade_band: str
    grade_band_label: str
    hotel_rate: Decimal
    hotel_amount: Decimal
    allowance_region: str
    meal_allowance_rate: Decimal
    basic_allowance_rate: Decimal
    total_allowance_rate: Decimal
    allowance_amount: Decimal
    transport_mode: str = ""
    transport_origin: str = ""
    transport_destination: str = ""
    transport_estimated_amount: Decimal = Decimal("0.00")
    transport_estimate_basis: str = ""
    transport_estimate_confidence: str = ""
    transport_estimate_source: str = ""
    transport_estimate_rule_code: str = ""
    transport_estimate_rule_name: str = ""
    transport_estimate_rule_version: str = ""
    travel_date: date | None = None
    total_amount: Decimal
    rule_name: str
    rule_version: str
    formula_text: str
    summary_text: str


class ExpenseClaimAttachmentActionResponse(BaseModel):
    message: str
    claim_id: str
    item_id: str
    invoice_id: str | None = None
    item_date: date | None = None
    item_type: str | None = None
    item_reason: str | None = None
    item_location: str | None = None
    item_note: str | None = None
    item_amount: Decimal | None = None
    claim_amount: Decimal | None = None
    claim_risk_flags: list[Any] = Field(default_factory=list)
    attachment: ExpenseClaimAttachmentRead | None = None


class ExpenseClaimItemActionResponse(BaseModel):
    message: str
    claim_id: str
    item_id: str
