from __future__ import annotations

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

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

from app.db.base_class import Base


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


class FinancialConnectorConfig(Base):
    """租户绑定的连接器契约；只保存服务端密钥引用，不保存密钥。"""

    __tablename__ = "financial_connector_configs"
    __table_args__ = (
        UniqueConstraint("tenant_id", "id", name="uq_financial_connector_configs_tenant_id"),
        UniqueConstraint(
            "tenant_id",
            "provider",
            "key_version",
            name="uq_financial_connector_configs_tenant_provider_key",
        ),
        CheckConstraint(
            "environment IN ('test', 'mock', 'staging', 'production')",
            name="ck_financial_connector_configs_environment",
        ),
        CheckConstraint(
            "status IN ('active', 'disabled', 'rotating')",
            name="ck_financial_connector_configs_status",
        ),
        CheckConstraint(
            "clock_skew_seconds BETWEEN 30 AND 900",
            name="ck_financial_connector_configs_clock_skew",
        ),
        CheckConstraint(
            "version >= 1",
            name="ck_financial_connector_configs_version",
        ),
        CheckConstraint(
            "length(trim(provider)) > 0 AND length(trim(key_version)) > 0 "
            "AND length(trim(secret_ref)) > 0",
            name="ck_financial_connector_configs_keys",
        ),
        Index(
            "ix_financial_connector_configs_tenant_status",
            "tenant_id",
            "status",
            "provider",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    provider: Mapped[str] = mapped_column(String(80), nullable=False)
    environment: Mapped[str] = mapped_column(String(16), nullable=False)
    key_version: Mapped[str] = mapped_column(String(40), nullable=False)
    secret_ref: Mapped[str] = mapped_column(String(180), nullable=False)
    allowed_event_types_json: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
    clock_skew_seconds: Mapped[int] = mapped_column(Integer, nullable=False, default=300)
    status: Mapped[str] = mapped_column(String(16), nullable=False, default="disabled")
    version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
    last_success_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    last_error_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    last_error_code: Mapped[str | None] = mapped_column(String(80))
    created_by: Mapped[str] = mapped_column(String(120), nullable=False)
    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 FinancialConnectorConfigEvent(Base):
    """连接器配置生命周期审计事实；不得保存密钥引用或密钥明文。"""

    __tablename__ = "financial_connector_config_events"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_financial_connector_config_events_tenant_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "request_id",
            "action",
            name="uq_financial_connector_config_events_tenant_request_action",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "config_id"],
            ["financial_connector_configs.tenant_id", "financial_connector_configs.id"],
            name="fk_financial_connector_config_events_tenant_config",
            ondelete="RESTRICT",
        ),
        CheckConstraint(
            "action IN ('created', 'activated', 'disabled', "
            "'rotation_started', 'rotation_replacement_created')",
            name="ck_financial_connector_config_events_action",
        ),
        CheckConstraint(
            "expected_version IS NULL OR expected_version >= 1",
            name="ck_financial_connector_config_events_expected_version",
        ),
        CheckConstraint(
            "length(trim(actor_id)) > 0 AND length(trim(request_id)) > 0 "
            "AND length(trim(reason)) > 0",
            name="ck_financial_connector_config_events_required_text",
        ),
        Index(
            "ix_financial_connector_config_events_tenant_config_time",
            "tenant_id",
            "config_id",
            "occurred_at",
        ),
        Index(
            "ix_financial_connector_config_events_tenant_request",
            "tenant_id",
            "request_id",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    config_id: Mapped[str] = mapped_column(String(36), nullable=False)
    action: Mapped[str] = mapped_column(String(40), nullable=False)
    actor_id: Mapped[str] = mapped_column(String(120), nullable=False)
    request_id: Mapped[str] = mapped_column(String(120), nullable=False)
    reason: Mapped[str] = mapped_column(Text(), nullable=False)
    expected_version: Mapped[int | None] = mapped_column(Integer)
    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)
    occurred_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )


class FinancialConnectorOperationalEvent(Base):
    """连接器重放、认证失败和载荷冲突的最小化追加式运营事实。"""

    __tablename__ = "financial_connector_operational_events"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_financial_connector_operational_events_tenant_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "idempotency_key",
            name="uq_financial_connector_operational_events_tenant_request",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "config_id"],
            ["financial_connector_configs.tenant_id", "financial_connector_configs.id"],
            name="fk_financial_connector_operational_events_tenant_config",
            ondelete="RESTRICT",
        ),
        CheckConstraint(
            "event_type IN ('replay', 'auth_failure', 'payload_conflict')",
            name="ck_financial_connector_operational_events_type",
        ),
        CheckConstraint(
            "environment IN ('test', 'mock', 'staging', 'production')",
            name="ck_financial_connector_operational_events_environment",
        ),
        CheckConstraint(
            "length(trim(provider)) > 0 AND length(trim(reason_code)) > 0",
            name="ck_financial_connector_operational_events_required_text",
        ),
        CheckConstraint(
            "length(request_fingerprint) = 76 "
            "AND request_fingerprint LIKE 'hmac-sha256:%' "
            "AND length(external_event_fingerprint) = 76 "
            "AND external_event_fingerprint LIKE 'hmac-sha256:%' "
            "AND length(idempotency_key) = 71 "
            "AND idempotency_key LIKE 'sha256:%'",
            name="ck_financial_connector_operational_events_fingerprints",
        ),
        Index(
            "ix_financial_connector_operational_events_tenant_config_time",
            "tenant_id",
            "config_id",
            "occurred_at",
        ),
        Index(
            "ix_financial_connector_operational_events_tenant_type_time",
            "tenant_id",
            "event_type",
            "occurred_at",
        ),
        Index(
            "ix_financial_connector_operational_events_tenant_provider_time",
            "tenant_id",
            "provider",
            "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)
    config_id: Mapped[str] = mapped_column(String(36), nullable=False)
    provider: Mapped[str] = mapped_column(String(80), nullable=False)
    environment: Mapped[str] = mapped_column(String(16), nullable=False)
    event_type: Mapped[str] = mapped_column(String(32), nullable=False)
    reason_code: Mapped[str] = mapped_column(String(80), nullable=False)
    request_fingerprint: Mapped[str] = mapped_column(String(76), nullable=False)
    external_event_fingerprint: Mapped[str] = mapped_column(String(76), nullable=False)
    idempotency_key: Mapped[str] = mapped_column(String(71), nullable=False)
    occurred_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )


class FinancialConnectorEvent(Base):
    """经签名验证的最小化外部事实。表由 PostgreSQL 触发器强制只追加。"""

    __tablename__ = "financial_connector_events"
    __table_args__ = (
        UniqueConstraint("tenant_id", "id", name="uq_financial_connector_events_tenant_id"),
        UniqueConstraint(
            "tenant_id",
            "provider",
            "external_event_id",
            name="uq_financial_connector_events_external_id",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "config_id"],
            ["financial_connector_configs.tenant_id", "financial_connector_configs.id"],
            name="fk_financial_connector_events_tenant_config",
            ondelete="RESTRICT",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "origin_event_id"],
            ["financial_connector_events.tenant_id", "financial_connector_events.id"],
            name="fk_financial_connector_events_tenant_origin",
            ondelete="RESTRICT",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "expense_case_id"],
            ["expense_cases.tenant_id", "expense_cases.id"],
            name="fk_financial_connector_events_tenant_expense_case",
            ondelete="RESTRICT",
        ),
        CheckConstraint(
            "direction = 'inbound'",
            name="ck_financial_connector_events_direction",
        ),
        CheckConstraint(
            "event_type IN ('payment_settled', 'payment_failed', 'erp_posted', "
            "'erp_posting_failed', 'payment_refunded', 'payment_reversed')",
            name="ck_financial_connector_events_type",
        ),
        CheckConstraint(
            "environment IN ('test', 'mock', 'staging', 'production')",
            name="ck_financial_connector_events_environment",
        ),
        CheckConstraint(
            "verification_level IN ('simulated', 'staging_verified', 'production_verified')",
            name="ck_financial_connector_events_verification",
        ),
        CheckConstraint(
            "processing_status IN ('processed', 'exception', 'pending')",
            name="ck_financial_connector_events_processing_status",
        ),
        CheckConstraint(
            "length(trim(external_event_id)) > 0 "
            "AND length(trim(request_fingerprint)) >= 16 "
            "AND length(trim(content_hash)) >= 16",
            name="ck_financial_connector_events_fingerprints",
        ),
        CheckConstraint(
            "(event_type IN ('payment_refunded', 'payment_reversed', "
            "'erp_posted', 'erp_posting_failed') "
            "AND (origin_event_id IS NOT NULL OR processing_status = 'exception')) "
            "OR (event_type IN ('payment_settled', 'payment_failed') "
            "AND origin_event_id IS NULL)",
            name="ck_financial_connector_events_origin",
        ),
        Index(
            "ix_financial_connector_events_tenant_received",
            "tenant_id",
            "received_at",
        ),
        Index(
            "ix_financial_connector_events_tenant_claim",
            "tenant_id",
            "claim_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)
    config_id: Mapped[str] = mapped_column(String(36), nullable=False)
    provider: Mapped[str] = mapped_column(String(80), nullable=False)
    environment: Mapped[str] = mapped_column(String(16), nullable=False)
    direction: Mapped[str] = mapped_column(String(12), nullable=False, default="inbound")
    external_event_id: Mapped[str] = mapped_column(String(160), nullable=False)
    event_type: Mapped[str] = mapped_column(String(40), nullable=False)
    occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    received_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )
    key_version: Mapped[str] = mapped_column(String(40), nullable=False)
    verification_level: Mapped[str] = mapped_column(String(32), nullable=False)
    request_fingerprint: Mapped[str] = mapped_column(String(80), nullable=False)
    content_hash: Mapped[str] = mapped_column(String(80), nullable=False)
    processing_status: Mapped[str] = mapped_column(String(20), nullable=False)
    error_code: Mapped[str | None] = mapped_column(String(80))
    # expense_claims 由 legacy bootstrap 创建，迁移表只保存经过租户 Case 校验的软引用。
    claim_id: Mapped[str | None] = mapped_column(String(36))
    expense_case_id: Mapped[str | None] = mapped_column(String(36))
    origin_event_id: Mapped[str | None] = mapped_column(String(36))
    correlation_id: Mapped[str] = mapped_column(String(64), nullable=False)
    external_reference_tail: Mapped[str | None] = mapped_column(String(8))
    normalized_payload_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)


class PaymentReconciliationCase(Base):
    """对账当前投影；所有历史变化由 PaymentReconciliationEvent 保存。"""

    __tablename__ = "payment_reconciliation_cases"
    __table_args__ = (
        UniqueConstraint("tenant_id", "id", name="uq_payment_reconciliation_cases_tenant_id"),
        UniqueConstraint(
            "tenant_id",
            "provider",
            "claim_id",
            name="uq_payment_reconciliation_cases_tenant_provider_claim",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "last_connector_event_id"],
            ["financial_connector_events.tenant_id", "financial_connector_events.id"],
            name="fk_payment_reconciliation_cases_tenant_last_event",
            ondelete="RESTRICT",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "expense_case_id"],
            ["expense_cases.tenant_id", "expense_cases.id"],
            name="fk_payment_reconciliation_cases_tenant_expense_case",
            ondelete="RESTRICT",
        ),
        CheckConstraint(
            "status IN ('pending', 'matched', 'exception', 'confirmed', "
            "'rejected', 'reopened', 'closed')",
            name="ck_payment_reconciliation_cases_status",
        ),
        CheckConstraint(
            "erp_status IN ('pending_posting', 'posted', 'posting_failed')",
            name="ck_payment_reconciliation_cases_erp_status",
        ),
        CheckConstraint(
            "expected_amount >= 0 AND actual_amount >= 0",
            name="ck_payment_reconciliation_cases_amounts",
        ),
        CheckConstraint(
            "length(trim(expected_currency)) = 3 AND length(trim(actual_currency)) = 3",
            name="ck_payment_reconciliation_cases_currencies",
        ),
        Index(
            "ix_payment_reconciliation_cases_tenant_status",
            "tenant_id",
            "status",
            "updated_at",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    provider: Mapped[str] = mapped_column(String(80), nullable=False)
    # 租户边界由 expense_case_id 复合外键与服务查询共同保证。
    claim_id: Mapped[str] = mapped_column(String(36), nullable=False)
    expense_case_id: Mapped[str | None] = mapped_column(String(36))
    expected_amount: Mapped[Decimal] = mapped_column(Numeric(20, 4), nullable=False)
    actual_amount: Mapped[Decimal] = mapped_column(Numeric(20, 4), nullable=False)
    amount_difference: Mapped[Decimal] = mapped_column(Numeric(20, 4), nullable=False)
    expected_currency: Mapped[str] = mapped_column(String(3), nullable=False)
    actual_currency: Mapped[str] = mapped_column(String(3), nullable=False)
    expected_reference: Mapped[str] = mapped_column(String(160), nullable=False)
    external_reference_tail: Mapped[str | None] = mapped_column(String(8))
    status: Mapped[str] = mapped_column(String(20), nullable=False)
    exception_code: Mapped[str | None] = mapped_column(String(80))
    erp_status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending_posting")
    erp_document_tail: Mapped[str | None] = mapped_column(String(8))
    erp_document_hash: Mapped[str | None] = mapped_column(String(80))
    assigned_to: Mapped[str | None] = mapped_column(String(120))
    last_connector_event_id: Mapped[str] = mapped_column(String(36), nullable=False)
    version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
    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 PaymentReconciliationEvent(Base):
    """对账动作审计事实；数据库级禁止更新和删除。"""

    __tablename__ = "payment_reconciliation_events"
    __table_args__ = (
        UniqueConstraint("tenant_id", "id", name="uq_payment_reconciliation_events_tenant_id"),
        UniqueConstraint(
            "tenant_id",
            "connector_event_id",
            "action",
            name="uq_payment_reconciliation_events_connector_action",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "reconciliation_case_id"],
            ["payment_reconciliation_cases.tenant_id", "payment_reconciliation_cases.id"],
            name="fk_payment_reconciliation_events_tenant_case",
            ondelete="RESTRICT",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "connector_event_id"],
            ["financial_connector_events.tenant_id", "financial_connector_events.id"],
            name="fk_payment_reconciliation_events_tenant_connector_event",
            ondelete="RESTRICT",
        ),
        CheckConstraint(
            "action IN ('auto_matched', 'exception_created', 'erp_posted', "
            "'erp_posting_failed', 'reopened', 'confirmed', 'rejected', 'closed')",
            name="ck_payment_reconciliation_events_action",
        ),
        CheckConstraint(
            "length(trim(request_fingerprint)) >= 16",
            name="ck_payment_reconciliation_events_fingerprint",
        ),
        Index(
            "ix_payment_reconciliation_events_tenant_case_time",
            "tenant_id",
            "reconciliation_case_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)
    reconciliation_case_id: Mapped[str] = mapped_column(String(36), nullable=False)
    connector_event_id: Mapped[str] = mapped_column(String(36), nullable=False)
    action: Mapped[str] = mapped_column(String(32), nullable=False)
    actor_type: Mapped[str] = mapped_column(String(20), nullable=False)
    actor_id: Mapped[str] = mapped_column(String(120), nullable=False)
    request_fingerprint: Mapped[str] = mapped_column(String(80), nullable=False)
    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)
    reason: Mapped[str | None] = mapped_column(Text())
    correlation_id: Mapped[str] = mapped_column(String(64), nullable=False)
    occurred_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )
