"""add blind negative audit samples and recall ground-truth labels

Revision ID: 20260716_0023
Revises: 20260716_0022
Create Date: 2026-07-16 23:50:00
"""

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

revision: str = "20260716_0023"
down_revision: str | None = "20260716_0022"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


_AUDIT_SAMPLE_TABLE = "agent_asset_release_audit_samples"
_LABEL_TABLE = "agent_asset_release_labels"


def _require_postgresql() -> None:
    dialect_name = op.get_bind().dialect.name
    if dialect_name != "postgresql":
        raise RuntimeError(
            "20260716_0023 only supports PostgreSQL; "
            f"refusing to mutate {dialect_name} without transactional constraint DDL"
        )


def _require_lossless_downgrade() -> None:
    bind = op.get_bind()
    audit_sample_count = int(
        bind.scalar(sa.text(f"SELECT COUNT(*) FROM {_AUDIT_SAMPLE_TABLE}")) or 0
    )
    extended_label_count = int(
        bind.scalar(
            sa.text(
                f"SELECT COUNT(*) FROM {_LABEL_TABLE} "
                "WHERE label IN ('risk_present', 'risk_absent') "
                "OR verification_source = 'blind_release_review'"
            )
        )
        or 0
    )
    if audit_sample_count or extended_label_count:
        raise RuntimeError(
            "cannot downgrade release blind audit: immutable audit evidence exists "
            f"(audit_samples={audit_sample_count}, extended_labels={extended_label_count})"
        )


def _replace_label_constraints(*, expanded: bool) -> None:
    if not expanded:
        op.drop_constraint(
            "ck_agent_asset_release_labels_semantics",
            _LABEL_TABLE,
            type_="check",
        )
    op.drop_constraint(
        "ck_agent_asset_release_labels_label",
        _LABEL_TABLE,
        type_="check",
    )
    op.drop_constraint(
        "ck_agent_asset_release_labels_source",
        _LABEL_TABLE,
        type_="check",
    )
    if expanded:
        label_values = (
            "label IN ('confirmed', 'false_positive', 'risk_present', 'risk_absent')"
        )
        source_values = (
            "verification_source IN ('typed_risk_disposition', 'release_review', "
            "'blind_release_review')"
        )
    else:
        label_values = "label IN ('confirmed', 'false_positive')"
        source_values = (
            "verification_source IN ('typed_risk_disposition', 'release_review')"
        )
    op.create_check_constraint(
        "ck_agent_asset_release_labels_label",
        _LABEL_TABLE,
        label_values,
    )
    op.create_check_constraint(
        "ck_agent_asset_release_labels_source",
        _LABEL_TABLE,
        source_values,
    )
    if expanded:
        op.create_check_constraint(
            "ck_agent_asset_release_labels_semantics",
            _LABEL_TABLE,
            "(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'))",
        )


def upgrade() -> None:
    _require_postgresql()
    _replace_label_constraints(expanded=True)
    op.create_table(
        _AUDIT_SAMPLE_TABLE,
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("tenant_id", sa.String(length=64), nullable=False),
        sa.Column("observation_id", sa.String(length=36), nullable=False),
        sa.Column("asset_id", sa.String(length=36), nullable=False),
        sa.Column("release_id", sa.String(length=64), nullable=False),
        sa.Column("stage", sa.String(length=16), nullable=False),
        sa.Column("version", sa.String(length=30), nullable=False),
        sa.Column("stratum", sa.String(length=40), nullable=False),
        sa.Column("sampling_probability_ppm", sa.Integer(), nullable=False),
        sa.Column("selection_score_ppm", sa.Integer(), nullable=False),
        sa.Column("source_reference_encrypted", sa.Text(), nullable=False),
        sa.Column("idempotency_key", sa.String(length=80), nullable=False),
        sa.Column("payload_fingerprint", sa.String(length=64), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.func.now(),
            nullable=False,
        ),
        sa.CheckConstraint(
            "stratum IN ('candidate_positive_census', "
            "'candidate_disagreement_census', 'candidate_negative_random')",
            name="ck_agent_asset_release_audit_samples_stratum",
        ),
        sa.CheckConstraint(
            "sampling_probability_ppm BETWEEN 1 AND 1000000",
            name="ck_agent_asset_release_audit_samples_probability",
        ),
        sa.CheckConstraint(
            "selection_score_ppm BETWEEN 0 AND 999999",
            name="ck_agent_asset_release_audit_samples_score",
        ),
        sa.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",
            ],
            name="fk_agent_asset_release_audit_samples_observation",
            ondelete="RESTRICT",
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_agent_asset_release_audit_samples_tenant_id",
        ),
        sa.UniqueConstraint(
            "tenant_id",
            "observation_id",
            name="uq_agent_asset_release_audit_samples_observation",
        ),
        sa.UniqueConstraint(
            "tenant_id",
            "idempotency_key",
            name="uq_agent_asset_release_audit_samples_idempotency",
        ),
    )
    op.create_index(
        "ix_agent_asset_release_audit_samples_release",
        _AUDIT_SAMPLE_TABLE,
        ["tenant_id", "asset_id", "release_id", "stage", "version", "created_at"],
    )
    op.execute(
        f"""
        CREATE TRIGGER trg_{_AUDIT_SAMPLE_TABLE}_append_only
        BEFORE UPDATE OR DELETE ON {_AUDIT_SAMPLE_TABLE}
        FOR EACH ROW EXECUTE FUNCTION reject_agent_asset_release_telemetry_mutation();
        """
    )


def downgrade() -> None:
    _require_postgresql()
    _require_lossless_downgrade()
    op.execute(
        f"DROP TRIGGER IF EXISTS trg_{_AUDIT_SAMPLE_TABLE}_append_only "
        f"ON {_AUDIT_SAMPLE_TABLE}"
    )
    op.drop_index(
        "ix_agent_asset_release_audit_samples_release",
        table_name=_AUDIT_SAMPLE_TABLE,
    )
    op.drop_table(_AUDIT_SAMPLE_TABLE)
    _replace_label_constraints(expanded=False)
