from __future__ import annotations

import uuid
from datetime import UTC, datetime, timedelta
from decimal import Decimal
from typing import Any

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

from app.db.base_class import Base

MEMORY_CANDIDATE_TTL_DAYS = 90
MEMORY_ACTIVE_TTL_DAYS = 180


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


def _candidate_expires_at() -> datetime:
    return datetime.now(UTC) + timedelta(days=MEMORY_CANDIDATE_TTL_DAYS)


class MemoryEntry(Base):
    """受证据约束、可审计且可撤销的分层费用申请记忆。"""

    __tablename__ = "memory_entries"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_memory_entries_tenant_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "scope_type",
            "scope_id",
            "scene",
            "field_key",
            "generation",
            name="uq_memory_entries_generation",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "superseded_by_id"],
            ["memory_entries.tenant_id", "memory_entries.id"],
            ondelete="RESTRICT",
            name="fk_memory_entries_tenant_superseded_by",
        ),
        CheckConstraint(
            "scope_type IN ('user', 'department', 'enterprise')",
            name="ck_memory_entries_scope_type",
        ),
        CheckConstraint(
            "origin_type IN ('learned', 'admin_managed')",
            name="ck_memory_entries_origin_type",
        ),
        CheckConstraint(
            "(scope_type = 'user' AND origin_type = 'learned') OR "
            "(scope_type IN ('department', 'enterprise') "
            "AND origin_type = 'admin_managed')",
            name="ck_memory_entries_scope_origin",
        ),
        CheckConstraint(
            "scope_type != 'enterprise' OR scope_id = tenant_id",
            name="ck_memory_entries_enterprise_scope",
        ),
        CheckConstraint(
            "(origin_type = 'learned' AND managed_by IS NULL "
            "AND managed_at IS NULL AND management_reason IS NULL) OR "
            "(origin_type = 'admin_managed' AND managed_by IS NOT NULL "
            "AND length(trim(managed_by)) > 0 AND managed_at IS NOT NULL "
            "AND management_reason IS NOT NULL "
            "AND length(trim(management_reason)) > 0 "
            "AND policy_version IS NOT NULL AND length(trim(policy_version)) > 0)",
            name="ck_memory_entries_management_audit",
        ),
        CheckConstraint(
            "scene = 'travel_application'",
            name="ck_memory_entries_scene",
        ),
        CheckConstraint(
            "field_key = 'transport_mode'",
            name="ck_memory_entries_field_key",
        ),
        CheckConstraint(
            "status IN ('candidate', 'active', 'suppressed', 'expired', 'revoked')",
            name="ck_memory_entries_status",
        ),
        CheckConstraint(
            "generation >= 1",
            name="ck_memory_entries_generation",
        ),
        CheckConstraint(
            "evidence_count >= 0 AND approved_evidence_count >= 0 "
            "AND approved_evidence_count <= evidence_count",
            name="ck_memory_entries_evidence_counts",
        ),
        CheckConstraint(
            "confidence >= 0 AND confidence <= 1",
            name="ck_memory_entries_confidence",
        ),
        CheckConstraint(
            "candidate_expires_at > created_at",
            name="ck_memory_entries_candidate_expiry",
        ),
        CheckConstraint(
            "active_expires_at IS NULL OR ("
            "activated_at IS NOT NULL AND active_expires_at > activated_at)",
            name="ck_memory_entries_active_expiry",
        ),
        CheckConstraint(
            "status != 'active' OR (activated_at IS NOT NULL AND active_expires_at IS NOT NULL)",
            name="ck_memory_entries_active_fields",
        ),
        CheckConstraint(
            "status != 'suppressed' OR suppressed_at IS NOT NULL",
            name="ck_memory_entries_suppressed_fields",
        ),
        CheckConstraint(
            "status != 'expired' OR expired_at IS NOT NULL",
            name="ck_memory_entries_expired_fields",
        ),
        CheckConstraint(
            "status != 'revoked' OR (revoked_at IS NOT NULL AND length(revoked_reason) > 0)",
            name="ck_memory_entries_revoked_fields",
        ),
        CheckConstraint(
            "superseded_by_id IS NULL OR status IN ('suppressed', 'revoked')",
            name="ck_memory_entries_superseded_status",
        ),
        CheckConstraint(
            "superseded_by_id IS NULL OR superseded_by_id != id",
            name="ck_memory_entries_not_self_superseded",
        ),
        CheckConstraint(
            "(management_request_id IS NULL AND management_payload_fingerprint IS NULL) OR "
            "(management_request_id IS NOT NULL "
            "AND management_payload_fingerprint IS NOT NULL)",
            name="ck_memory_entries_management_idempotency_pair",
        ),
        CheckConstraint(
            "(revoke_request_id IS NULL AND revoke_payload_fingerprint IS NULL) OR "
            "(revoke_request_id IS NOT NULL AND revoke_payload_fingerprint IS NOT NULL)",
            name="ck_memory_entries_revoke_idempotency_pair",
        ),
        Index(
            "ix_memory_entries_scope_lookup",
            "tenant_id",
            "scope_type",
            "scope_id",
            "scene",
            "field_key",
            "status",
        ),
        Index(
            "ix_memory_entries_status_expiry",
            "tenant_id",
            "status",
            "candidate_expires_at",
            "active_expires_at",
        ),
        Index(
            "uq_memory_entries_management_request",
            "tenant_id",
            "management_request_id",
            unique=True,
        ),
        Index(
            "uq_memory_entries_revoke_request",
            "tenant_id",
            "revoke_request_id",
            unique=True,
        ),
        Index(
            "uq_memory_entries_active_scope",
            "tenant_id",
            "scope_type",
            "scope_id",
            "scene",
            "field_key",
            unique=True,
            postgresql_where=text(
                "status = 'active' "
                "AND scope_type IN ('department', 'enterprise')"
            ),
        ).ddl_if(dialect="postgresql"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    scope_type: Mapped[str] = mapped_column(
        String(20),
        nullable=False,
        default="user",
        server_default="user",
    )
    scope_id: Mapped[str] = mapped_column(String(120), nullable=False)
    origin_type: Mapped[str] = mapped_column(
        String(24),
        nullable=False,
        default="learned",
        server_default="learned",
    )
    managed_by: Mapped[str | None] = mapped_column(String(255), nullable=True)
    managed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    management_reason: Mapped[str | None] = mapped_column(String(255), nullable=True)
    policy_version: Mapped[str | None] = mapped_column(String(64), nullable=True)
    scene: Mapped[str] = mapped_column(
        String(50),
        nullable=False,
        default="travel_application",
        server_default="travel_application",
    )
    field_key: Mapped[str] = mapped_column(
        String(60),
        nullable=False,
        default="transport_mode",
        server_default="transport_mode",
    )
    generation: Mapped[int] = mapped_column(
        Integer,
        nullable=False,
        default=1,
        server_default="1",
    )
    value_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
    value_fingerprint: Mapped[str] = mapped_column(String(80), nullable=False)
    status: Mapped[str] = mapped_column(
        String(20),
        nullable=False,
        default="candidate",
        server_default="candidate",
    )
    evidence_count: Mapped[int] = mapped_column(
        Integer,
        nullable=False,
        default=0,
        server_default="0",
    )
    approved_evidence_count: Mapped[int] = mapped_column(
        Integer,
        nullable=False,
        default=0,
        server_default="0",
    )
    confidence: Mapped[Decimal] = mapped_column(
        Numeric(5, 4),
        nullable=False,
        default=Decimal("0.0000"),
        server_default="0",
    )
    last_evidence_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        default=lambda: datetime.now(UTC),
        server_default=func.now(),
    )
    candidate_expires_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        default=_candidate_expires_at,
    )
    activated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    active_expires_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True),
        nullable=True,
    )
    suppressed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    expired_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    revoked_reason: Mapped[str | None] = mapped_column(String(255), nullable=True)
    superseded_by_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
    management_request_id: Mapped[str | None] = mapped_column(String(120), nullable=True)
    management_payload_fingerprint: Mapped[str | None] = mapped_column(
        String(80),
        nullable=True,
    )
    revoke_request_id: Mapped[str | None] = mapped_column(String(120), nullable=True)
    revoke_payload_fingerprint: Mapped[str | None] = mapped_column(
        String(80),
        nullable=True,
    )
    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(),
    )


class MemoryEvidenceLink(Base):
    """一条记忆在一个费用 Case 中的去重证据包。"""

    __tablename__ = "memory_evidence_links"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_memory_evidence_links_tenant_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "memory_entry_id",
            "expense_case_id",
            name="uq_memory_evidence_links_entry_case",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "memory_entry_id"],
            ["memory_entries.tenant_id", "memory_entries.id"],
            ondelete="RESTRICT",
            name="fk_memory_evidence_links_tenant_entry",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "expense_case_id"],
            ["expense_cases.tenant_id", "expense_cases.id"],
            ondelete="RESTRICT",
            name="fk_memory_evidence_links_tenant_case",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "decision_id"],
            ["ai_decisions.tenant_id", "ai_decisions.id"],
            ondelete="RESTRICT",
            name="fk_memory_evidence_links_tenant_decision",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "feedback_id"],
            ["ai_decision_feedback.tenant_id", "ai_decision_feedback.id"],
            ondelete="RESTRICT",
            name="fk_memory_evidence_links_tenant_feedback",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "outcome_id"],
            ["workflow_outcomes.tenant_id", "workflow_outcomes.id"],
            ondelete="RESTRICT",
            name="fk_memory_evidence_links_tenant_outcome",
        ),
        Index(
            "ix_memory_evidence_links_entry_time",
            "tenant_id",
            "memory_entry_id",
            "created_at",
        ),
        Index(
            "ix_memory_evidence_links_sources",
            "tenant_id",
            "decision_id",
            "feedback_id",
            "outcome_id",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    memory_entry_id: Mapped[str] = mapped_column(String(36), nullable=False)
    expense_case_id: Mapped[str] = mapped_column(String(36), nullable=False)
    decision_id: Mapped[str] = mapped_column(String(36), nullable=False)
    feedback_id: Mapped[str] = mapped_column(String(36), nullable=False)
    outcome_id: Mapped[str] = mapped_column(String(36), nullable=False)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        server_default=func.now(),
    )
