from __future__ import annotations

import uuid
from datetime import datetime
from decimal import Decimal
from typing import Any

from sqlalchemy import (
    Boolean,
    CheckConstraint,
    DateTime,
    ForeignKeyConstraint,
    Index,
    Integer,
    Numeric,
    String,
    Text,
    UniqueConstraint,
    func,
    text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON

from app.db.base_class import Base


def _new_id() -> str:
    return str(uuid.uuid4())


class ApprovalTask(Base):
    """审批节点的个人工作项投影，不承载 BPMN 流程定义。"""

    __tablename__ = "approval_tasks"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_approval_tasks_tenant_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "node_entry_key",
            name="uq_approval_tasks_tenant_node_entry",
        ),
        UniqueConstraint(
            "tenant_id",
            "node_instance_id",
            "assignee_kind",
            "assignee_key",
            name="uq_approval_tasks_node_participant",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "parent_task_id"],
            ["approval_tasks.tenant_id", "approval_tasks.id"],
            ondelete="RESTRICT",
            name="fk_approval_tasks_tenant_parent",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "expense_case_id"],
            ["expense_cases.tenant_id", "expense_cases.id"],
            ondelete="RESTRICT",
            name="fk_approval_tasks_tenant_expense_case",
        ),
        CheckConstraint(
            "task_kind IN ('root', 'add_sign', 'countersign')",
            name="ck_approval_tasks_task_kind",
        ),
        CheckConstraint(
            "node_key IN ('direct_manager', 'budget_manager', 'finance')",
            name="ck_approval_tasks_node_key",
        ),
        CheckConstraint(
            "coordination_mode IN ('single', 'sequential_before_owner', 'parallel_all')",
            name="ck_approval_tasks_coordination_mode",
        ),
        CheckConstraint(
            "owner_kind IN ('employee', 'role')",
            name="ck_approval_tasks_owner_kind",
        ),
        CheckConstraint(
            "assignee_kind IN ('employee', 'role')",
            name="ck_approval_tasks_assignee_kind",
        ),
        CheckConstraint(
            "status IN ('waiting', 'pending', 'completed', 'returned', "
            "'cancelled', 'superseded')",
            name="ck_approval_tasks_status",
        ),
        CheckConstraint(
            "decision IS NULL OR decision IN ('approved', 'returned')",
            name="ck_approval_tasks_decision",
        ),
        CheckConstraint(
            "entered_at_source IN ('workflow_event', 'submitted_at', 'updated_at', 'backfill')",
            name="ck_approval_tasks_entered_at_source",
        ),
        CheckConstraint(
            "priority_tier IN ('normal', 'high', 'urgent')",
            name="ck_approval_tasks_priority_tier",
        ),
        CheckConstraint(
            "risk_level IN ('low', 'medium', 'high', 'critical')",
            name="ck_approval_tasks_risk_level",
        ),
        CheckConstraint(
            "(task_kind = 'root' AND parent_task_id IS NULL AND sequence_order = 0) OR "
            "(task_kind IN ('add_sign', 'countersign') "
            "AND parent_task_id IS NOT NULL AND sequence_order > 0)",
            name="ck_approval_tasks_parent_shape",
        ),
        CheckConstraint(
            "(status = 'completed' AND decision = 'approved' "
            "AND completed_at IS NOT NULL AND cancelled_at IS NULL) OR "
            "(status = 'returned' AND decision = 'returned' "
            "AND completed_at IS NOT NULL AND cancelled_at IS NULL) OR "
            "(status IN ('cancelled', 'superseded') AND decision IS NULL "
            "AND completed_at IS NULL AND cancelled_at IS NOT NULL) OR "
            "(status IN ('waiting', 'pending') AND decision IS NULL "
            "AND completed_at IS NULL AND cancelled_at IS NULL)",
            name="ck_approval_tasks_lifecycle",
        ),
        CheckConstraint(
            "status NOT IN ('pending', 'completed', 'returned') OR activated_at IS NOT NULL",
            name="ck_approval_tasks_activation",
        ),
        CheckConstraint(
            "delegation_expires_at IS NULL OR delegated_by IS NOT NULL",
            name="ck_approval_tasks_delegation",
        ),
        CheckConstraint("version >= 1", name="ck_approval_tasks_version"),
        CheckConstraint("node_sequence >= 1", name="ck_approval_tasks_node_sequence"),
        CheckConstraint("sla_hours_snapshot > 0", name="ck_approval_tasks_sla_hours"),
        CheckConstraint("due_at >= entered_at", name="ck_approval_tasks_due_at"),
        CheckConstraint("escalation_level >= 0", name="ck_approval_tasks_escalation"),
        CheckConstraint(
            "priority_score >= 0 AND priority_score <= 100",
            name="ck_approval_tasks_priority_score",
        ),
        CheckConstraint(
            "open_risk_count >= 0",
            name="ck_approval_tasks_open_risk_count",
        ),
        CheckConstraint(
            "evidence_completeness >= 0 AND evidence_completeness <= 1",
            name="ck_approval_tasks_evidence_completeness",
        ),
        Index(
            "uq_approval_tasks_open_root_per_claim",
            "tenant_id",
            "claim_id",
            unique=True,
            postgresql_where=text(
                "task_kind = 'root' AND status IN ('waiting', 'pending')"
            ),
        ).ddl_if(dialect="postgresql"),
        Index(
            "ix_approval_tasks_personal_inbox",
            "tenant_id",
            "assignee_kind",
            "assignee_key",
            "status",
            "due_at",
        ),
        Index(
            "ix_approval_tasks_tenant_queue",
            "tenant_id",
            "status",
            "priority_score",
            "due_at",
        ),
        Index(
            "ix_approval_tasks_tenant_claim",
            "tenant_id",
            "claim_id",
            "node_sequence",
        ),
        Index(
            "ix_approval_tasks_tenant_node",
            "tenant_id",
            "node_instance_id",
            "sequence_order",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    claim_id: Mapped[str] = mapped_column(String(36), nullable=False)
    expense_case_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
    node_instance_id: Mapped[str] = mapped_column(String(120), nullable=False)
    node_entry_key: Mapped[str] = mapped_column(String(160), nullable=False)
    parent_task_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
    task_kind: Mapped[str] = mapped_column(String(24), nullable=False)
    node_key: Mapped[str] = mapped_column(String(32), nullable=False)
    node_label: Mapped[str] = mapped_column(String(120), nullable=False)
    node_sequence: Mapped[int] = mapped_column(Integer, nullable=False)
    sequence_order: Mapped[int] = mapped_column(
        Integer, nullable=False, default=0, server_default="0"
    )
    coordination_mode: Mapped[str] = mapped_column(String(32), nullable=False)
    owner_kind: Mapped[str] = mapped_column(String(20), nullable=False)
    owner_key: Mapped[str] = mapped_column(String(120), nullable=False)
    owner_employee_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
    owner_name: Mapped[str] = mapped_column(String(120), nullable=False)
    assignee_kind: Mapped[str] = mapped_column(String(20), nullable=False)
    assignee_key: Mapped[str] = mapped_column(String(120), nullable=False)
    assignee_employee_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
    assignee_name: Mapped[str] = mapped_column(String(120), nullable=False)
    delegated_by: Mapped[str | None] = mapped_column(String(120), nullable=True)
    delegation_expires_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    status: Mapped[str] = mapped_column(
        String(24), nullable=False, default="waiting", server_default="waiting"
    )
    decision: Mapped[str | None] = mapped_column(String(20), nullable=True)
    opinion: Mapped[str | None] = mapped_column(Text(), nullable=True)
    version: Mapped[int] = mapped_column(Integer, nullable=False, default=1, server_default="1")
    claim_status_snapshot: Mapped[str] = mapped_column(String(30), nullable=False)
    claim_stage_snapshot: Mapped[str] = mapped_column(String(50), nullable=False)
    entered_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    entered_at_source: Mapped[str] = mapped_column(String(24), nullable=False)
    activated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    sla_hours_snapshot: Mapped[int] = mapped_column(Integer, nullable=False)
    due_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    cancelled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    escalation_level: Mapped[int] = mapped_column(
        Integer, nullable=False, default=0, server_default="0"
    )
    escalated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    next_escalation_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    priority_score: Mapped[int] = mapped_column(
        Integer, nullable=False, default=0, server_default="0"
    )
    priority_tier: Mapped[str] = mapped_column(
        String(16), nullable=False, default="normal", server_default="normal"
    )
    priority_reasons_json: Mapped[list[dict[str, Any]]] = mapped_column(
        JSON, nullable=False, default=list
    )
    risk_level: Mapped[str] = mapped_column(
        String(16), nullable=False, default="low", server_default="low"
    )
    open_risk_count: Mapped[int] = mapped_column(
        Integer, nullable=False, default=0, server_default="0"
    )
    evidence_completeness: Mapped[Decimal] = mapped_column(
        Numeric(5, 4), nullable=False, default=Decimal("0"), server_default="0"
    )
    batch_eligible: Mapped[bool] = mapped_column(
        Boolean, nullable=False, default=False, server_default="false"
    )
    batch_block_reasons_json: Mapped[list[str]] = mapped_column(
        JSON, nullable=False, default=list
    )
    projection_updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        server_default=func.now(),
        onupdate=func.now(),
    )

    claim = relationship(
        "ExpenseClaim",
        primaryjoin="foreign(ApprovalTask.claim_id) == ExpenseClaim.id",
        foreign_keys=[claim_id],
        viewonly=True,
    )
    events = relationship(
        "ApprovalTaskEvent",
        back_populates="task",
        order_by="asc(ApprovalTaskEvent.occurred_at)",
        passive_deletes=True,
    )


class ApprovalTaskEvent(Base):
    """审批任务的不可变事实记录；所有写动作都以事件响应支持幂等重放。"""

    __tablename__ = "approval_task_events"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "actor_id",
            "request_id",
            name="uq_approval_task_events_actor_request",
        ),
        UniqueConstraint(
            "tenant_id",
            "task_id",
            "result_task_version",
            name="uq_approval_task_events_task_version",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "task_id"],
            ["approval_tasks.tenant_id", "approval_tasks.id"],
            ondelete="RESTRICT",
            name="fk_approval_task_events_tenant_task",
        ),
        CheckConstraint(
            "length(trim(event_type)) > 0",
            name="ck_approval_task_events_type",
        ),
        CheckConstraint(
            "actor_type IN ('user', 'system', 'agent', 'service')",
            name="ck_approval_task_events_actor_type",
        ),
        CheckConstraint(
            "expected_task_version >= 0 AND result_task_version >= 1 "
            "AND result_task_version >= expected_task_version",
            name="ck_approval_task_events_version",
        ),
        Index(
            "ix_approval_task_events_tenant_task_time",
            "tenant_id",
            "task_id",
            "occurred_at",
        ),
        Index(
            "ix_approval_task_events_tenant_node_time",
            "tenant_id",
            "node_instance_id",
            "occurred_at",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    task_id: Mapped[str] = mapped_column(String(36), nullable=False)
    node_instance_id: Mapped[str] = mapped_column(String(120), nullable=False)
    event_type: Mapped[str] = mapped_column(String(50), nullable=False)
    actor_id: Mapped[str] = mapped_column(String(120), nullable=False)
    actor_name: Mapped[str] = mapped_column(String(120), nullable=False)
    actor_type: Mapped[str] = mapped_column(String(20), nullable=False)
    request_id: Mapped[str] = mapped_column(String(120), nullable=False)
    expected_task_version: Mapped[int] = mapped_column(Integer, nullable=False)
    result_task_version: Mapped[int] = mapped_column(Integer, nullable=False)
    payload_fingerprint: Mapped[str] = mapped_column(String(80), nullable=False)
    payload_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
    before_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
    after_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
    response_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
    approval_action_ledger_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
    business_event_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
    correlation_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
    causation_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
    occurred_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )

    task = relationship("ApprovalTask", back_populates="events")
