"""Agent 资产分阶段发布的只追加运行观察与人工标签。"""

from __future__ import annotations

import uuid
from datetime import datetime

from sqlalchemy import (
    Boolean,
    CheckConstraint,
    DateTime,
    ForeignKeyConstraint,
    Index,
    Integer,
    String,
    Text,
    UniqueConstraint,
    event,
    func,
)
from sqlalchemy.orm import Mapped, mapped_column

from app.db.base_class import Base


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


class AgentAssetReleaseObservation(Base):
    """一次真实规则执行的去敏事实；同一来源重放只能得到同一条记录。"""

    __tablename__ = "agent_asset_release_observations"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_agent_asset_release_observations_tenant_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "idempotency_key",
            name="uq_agent_asset_release_observations_tenant_idempotency",
        ),
        UniqueConstraint(
            "tenant_id",
            "id",
            "asset_id",
            "release_id",
            "stage",
            "version",
            name="uq_agent_asset_release_observations_release_identity",
        ),
        CheckConstraint(
            "stage IN ('shadow', 'canary', 'active')",
            name="ck_agent_asset_release_observations_stage",
        ),
        CheckConstraint(
            "runtime_status IN ('completed', 'failed')",
            name="ck_agent_asset_release_observations_runtime_status",
        ),
        CheckConstraint(
            "source_kind IN ('expense_claim_risk')",
            name="ck_agent_asset_release_observations_source_kind",
        ),
        Index(
            "ix_agent_asset_release_observations_release",
            "tenant_id",
            "asset_id",
            "release_id",
            "stage",
            "version",
            "created_at",
        ),
        Index(
            "ix_agent_asset_release_observations_source",
            "tenant_id",
            "source_fingerprint",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    asset_id: Mapped[str] = mapped_column(String(36), nullable=False)
    release_id: Mapped[str] = mapped_column(String(64), nullable=False)
    stage: Mapped[str] = mapped_column(String(16), nullable=False)
    version: Mapped[str] = mapped_column(String(30), nullable=False)
    rule_code: Mapped[str] = mapped_column(String(100), nullable=False)
    business_stage: Mapped[str] = mapped_column(String(40), nullable=False)
    source_kind: Mapped[str] = mapped_column(
        String(32),
        nullable=False,
        default="expense_claim_risk",
        server_default="expense_claim_risk",
    )
    # 只保存租户、单据和规则的不可逆指纹，不保存单号、事由或票据内容。
    source_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
    candidate_hit: Mapped[bool] = mapped_column(Boolean, nullable=False)
    baseline_hit: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
    runtime_status: Mapped[str] = mapped_column(String(16), nullable=False)
    failure_code: Mapped[str] = mapped_column(
        String(40),
        nullable=False,
        default="none",
        server_default="none",
    )
    idempotency_key: Mapped[str] = mapped_column(String(80), nullable=False)
    payload_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        server_default=func.now(),
    )


class AgentAssetReleaseLabel(Base):
    """对运行观察的人工真值标签；纠正通过追加新标签完成。"""

    __tablename__ = "agent_asset_release_labels"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_agent_asset_release_labels_tenant_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "idempotency_key",
            name="uq_agent_asset_release_labels_tenant_idempotency",
        ),
        CheckConstraint(
            "label IN ('confirmed', 'false_positive', 'risk_present', 'risk_absent')",
            name="ck_agent_asset_release_labels_label",
        ),
        CheckConstraint(
            "verification_source IN ('typed_risk_disposition', 'release_review', "
            "'blind_release_review')",
            name="ck_agent_asset_release_labels_source",
        ),
        CheckConstraint(
            "(verification_source = 'blind_release_review' "
            "AND label IN ('risk_present', 'risk_absent')) OR "
            "(verification_source IN ('typed_risk_disposition', 'release_review') "
            "AND label IN ('confirmed', 'false_positive'))",
            name="ck_agent_asset_release_labels_semantics",
        ),
        ForeignKeyConstraint(
            [
                "tenant_id",
                "observation_id",
                "asset_id",
                "release_id",
                "stage",
                "version",
            ],
            [
                "agent_asset_release_observations.tenant_id",
                "agent_asset_release_observations.id",
                "agent_asset_release_observations.asset_id",
                "agent_asset_release_observations.release_id",
                "agent_asset_release_observations.stage",
                "agent_asset_release_observations.version",
            ],
            ondelete="RESTRICT",
            name="fk_agent_asset_release_labels_release_observation",
        ),
        Index(
            "ix_agent_asset_release_labels_observation_time",
            "tenant_id",
            "observation_id",
            "created_at",
        ),
        Index(
            "ix_agent_asset_release_labels_release",
            "tenant_id",
            "asset_id",
            "release_id",
            "stage",
            "version",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    observation_id: Mapped[str] = mapped_column(String(36), nullable=False)
    asset_id: Mapped[str] = mapped_column(String(36), nullable=False)
    release_id: Mapped[str] = mapped_column(String(64), nullable=False)
    stage: Mapped[str] = mapped_column(String(16), nullable=False)
    version: Mapped[str] = mapped_column(String(30), nullable=False)
    label: Mapped[str] = mapped_column(String(24), nullable=False)
    verification_source: Mapped[str] = mapped_column(String(32), nullable=False)
    # 事件和操作者只保留租户内稳定指纹，避免把账号标识带入评测数据集。
    source_event_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
    actor_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
    idempotency_key: Mapped[str] = mapped_column(String(80), nullable=False)
    payload_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        server_default=func.now(),
    )


class AgentAssetReleaseAuditSample(Base):
    """发布盲审样本；模型结论和业务来源只在服务端受控解析。"""

    __tablename__ = "agent_asset_release_audit_samples"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_agent_asset_release_audit_samples_tenant_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "observation_id",
            name="uq_agent_asset_release_audit_samples_observation",
        ),
        UniqueConstraint(
            "tenant_id",
            "idempotency_key",
            name="uq_agent_asset_release_audit_samples_idempotency",
        ),
        CheckConstraint(
            "stratum IN ('candidate_positive_census', "
            "'candidate_disagreement_census', 'candidate_negative_random')",
            name="ck_agent_asset_release_audit_samples_stratum",
        ),
        CheckConstraint(
            "sampling_probability_ppm BETWEEN 1 AND 1000000",
            name="ck_agent_asset_release_audit_samples_probability",
        ),
        CheckConstraint(
            "selection_score_ppm BETWEEN 0 AND 999999",
            name="ck_agent_asset_release_audit_samples_score",
        ),
        ForeignKeyConstraint(
            [
                "tenant_id",
                "observation_id",
                "asset_id",
                "release_id",
                "stage",
                "version",
            ],
            [
                "agent_asset_release_observations.tenant_id",
                "agent_asset_release_observations.id",
                "agent_asset_release_observations.asset_id",
                "agent_asset_release_observations.release_id",
                "agent_asset_release_observations.stage",
                "agent_asset_release_observations.version",
            ],
            ondelete="RESTRICT",
            name="fk_agent_asset_release_audit_samples_observation",
        ),
        Index(
            "ix_agent_asset_release_audit_samples_release",
            "tenant_id",
            "asset_id",
            "release_id",
            "stage",
            "version",
            "created_at",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    observation_id: Mapped[str] = mapped_column(String(36), nullable=False)
    asset_id: Mapped[str] = mapped_column(String(36), nullable=False)
    release_id: Mapped[str] = mapped_column(String(64), nullable=False)
    stage: Mapped[str] = mapped_column(String(16), nullable=False)
    version: Mapped[str] = mapped_column(String(30), nullable=False)
    stratum: Mapped[str] = mapped_column(String(40), nullable=False)
    sampling_probability_ppm: Mapped[int] = mapped_column(Integer, nullable=False)
    selection_score_ppm: Mapped[int] = mapped_column(Integer, nullable=False)
    source_reference_encrypted: Mapped[str] = mapped_column(Text, nullable=False)
    idempotency_key: Mapped[str] = mapped_column(String(80), nullable=False)
    payload_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        server_default=func.now(),
    )


def _reject_mutation(_mapper: object, _connection: object, target: object) -> None:
    raise ValueError(f"{type(target).__name__} is append-only and cannot be mutated.")


for _model in (
    AgentAssetReleaseObservation,
    AgentAssetReleaseLabel,
    AgentAssetReleaseAuditSample,
):
    event.listen(_model, "before_update", _reject_mutation)
    event.listen(_model, "before_delete", _reject_mutation)
