"""add versioned financial connector config lifecycle audit

Revision ID: 20260716_0020
Revises: 20260716_0019
Create Date: 2026-07-16 22:40:00
"""

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

revision: str = "20260716_0020"
down_revision: str | None = "20260716_0019"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


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


def _require_empty_lifecycle_for_downgrade() -> None:
    bind = op.get_bind()
    counts = {
        table_name: int(bind.scalar(sa.text(f"SELECT COUNT(*) FROM {table_name}")) or 0)
        for table_name in (
            "financial_connector_configs",
            "financial_connector_config_events",
        )
    }
    if any(counts.values()):
        summary = ", ".join(f"{name}={count}" for name, count in counts.items())
        raise RuntimeError(
            "cannot downgrade financial connector lifecycle: versioned configuration "
            f"state or immutable audit facts exist ({summary})"
        )


def upgrade() -> None:
    _require_postgresql()
    # 0017 曾冗余保存完整 claim_reference。这里以受控迁移完成一次性脱敏，
    # 内容哈希和签名指纹仍足以证明原始回执，运行期触发器随后立即恢复。
    op.execute(
        "ALTER TABLE financial_connector_events "
        "DISABLE TRIGGER trg_financial_connector_events_append_only"
    )
    op.execute(
        "UPDATE financial_connector_events "
        "SET normalized_payload_json = "
        "(normalized_payload_json::jsonb - 'claim_reference')::json "
        "WHERE normalized_payload_json::jsonb ? 'claim_reference'"
    )
    op.execute(
        "UPDATE financial_connector_events SET response_json = "
        "jsonb_set(response_json::jsonb, '{projection_scope}', "
        "to_jsonb((CASE WHEN verification_level = 'production_verified' "
        "THEN 'canonical' "
        "WHEN COALESCE(response_json::jsonb ->> 'reconciliation_case_id', '') <> '' "
        "THEN 'legacy_nonproduction_effect_unknown' "
        "ELSE 'simulation_only' END)::text), true)::json "
        "WHERE NOT (response_json::jsonb ? 'projection_scope')"
    )
    op.execute(
        "ALTER TABLE financial_connector_events "
        "ENABLE TRIGGER trg_financial_connector_events_append_only"
    )
    op.add_column(
        "financial_connector_configs",
        sa.Column("version", sa.Integer(), server_default="1", nullable=False),
    )
    op.create_check_constraint(
        "ck_financial_connector_configs_version",
        "financial_connector_configs",
        "version >= 1",
    )
    op.alter_column(
        "financial_connector_configs",
        "version",
        existing_type=sa.Integer(),
        server_default=None,
    )

    op.create_table(
        "financial_connector_config_events",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("tenant_id", sa.String(length=64), nullable=False),
        sa.Column("config_id", sa.String(length=36), nullable=False),
        sa.Column("action", sa.String(length=40), nullable=False),
        sa.Column("actor_id", sa.String(length=120), nullable=False),
        sa.Column("request_id", sa.String(length=120), nullable=False),
        sa.Column("reason", sa.Text(), nullable=False),
        sa.Column("expected_version", sa.Integer(), nullable=True),
        sa.Column("before_json", sa.JSON(), nullable=False),
        sa.Column("after_json", sa.JSON(), nullable=False),
        sa.Column(
            "occurred_at",
            sa.DateTime(timezone=True),
            server_default=sa.func.now(),
            nullable=False,
        ),
        sa.CheckConstraint(
            "action IN ('created', 'activated', 'disabled', "
            "'rotation_started', 'rotation_replacement_created')",
            name="ck_financial_connector_config_events_action",
        ),
        sa.CheckConstraint(
            "expected_version IS NULL OR expected_version >= 1",
            name="ck_financial_connector_config_events_expected_version",
        ),
        sa.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",
        ),
        sa.ForeignKeyConstraint(
            ["tenant_id", "config_id"],
            ["financial_connector_configs.tenant_id", "financial_connector_configs.id"],
            name="fk_financial_connector_config_events_tenant_config",
            ondelete="RESTRICT",
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_financial_connector_config_events_tenant_id",
        ),
        sa.UniqueConstraint(
            "tenant_id",
            "request_id",
            "action",
            name="uq_financial_connector_config_events_tenant_request_action",
        ),
    )
    op.create_index(
        "ix_financial_connector_config_events_tenant_config_time",
        "financial_connector_config_events",
        ["tenant_id", "config_id", "occurred_at"],
    )
    op.create_index(
        "ix_financial_connector_config_events_tenant_request",
        "financial_connector_config_events",
        ["tenant_id", "request_id"],
    )
    op.execute(
        """
        CREATE TRIGGER trg_financial_connector_config_events_append_only
        BEFORE UPDATE OR DELETE ON financial_connector_config_events
        FOR EACH ROW EXECUTE FUNCTION reject_financial_connector_append_only_mutation();
        """
    )


def downgrade() -> None:
    _require_postgresql()
    _require_empty_lifecycle_for_downgrade()
    op.execute(
        "DROP TRIGGER IF EXISTS trg_financial_connector_config_events_append_only "
        "ON financial_connector_config_events"
    )
    op.drop_index(
        "ix_financial_connector_config_events_tenant_request",
        table_name="financial_connector_config_events",
    )
    op.drop_index(
        "ix_financial_connector_config_events_tenant_config_time",
        table_name="financial_connector_config_events",
    )
    op.drop_table("financial_connector_config_events")
    op.drop_constraint(
        "ck_financial_connector_configs_version",
        "financial_connector_configs",
        type_="check",
    )
    op.drop_column("financial_connector_configs", "version")
