"""add reversible work-record identifier migration support

Revision ID: b8d4f0a2c6e1
Revises: a7c3e9f1b4d2
Create Date: 2026-08-22

"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects.postgresql import UUID

revision = "b8d4f0a2c6e1"
down_revision = "a7c3e9f1b4d2"
branch_labels = None
depends_on = None


_CASCADE_REFERENCES = """
DO $$
DECLARE
    fk RECORD;
    updated_definition TEXT;
BEGIN
    FOR fk IN
        SELECT
            constraint_row.conrelid::regclass AS relation_name,
            constraint_row.conname AS constraint_name,
            pg_get_constraintdef(constraint_row.oid) AS definition
        FROM pg_constraint AS constraint_row
        WHERE constraint_row.contype = 'f'
          AND constraint_row.confrelid IN (
              'workplans'::regclass,
              'tasks'::regclass
          )
    LOOP
        updated_definition := regexp_replace(
            fk.definition,
            ' ON UPDATE (NO ACTION|RESTRICT|CASCADE|SET NULL|SET DEFAULT)',
            '',
            'i'
        );
        IF position(' ON DELETE ' IN updated_definition) > 0 THEN
            updated_definition := regexp_replace(
                updated_definition,
                ' ON DELETE ',
                ' ON UPDATE CASCADE ON DELETE ',
                'i'
            );
        ELSE
            updated_definition := updated_definition || ' ON UPDATE CASCADE';
        END IF;

        EXECUTE format(
            'ALTER TABLE %s DROP CONSTRAINT %I',
            fk.relation_name,
            fk.constraint_name
        );
        EXECUTE format(
            'ALTER TABLE %s ADD CONSTRAINT %I %s',
            fk.relation_name,
            fk.constraint_name,
            updated_definition
        );
    END LOOP;
END $$;
"""

_REMOVE_UPDATE_CASCADE = """
DO $$
DECLARE
    fk RECORD;
    updated_definition TEXT;
BEGIN
    FOR fk IN
        SELECT
            constraint_row.conrelid::regclass AS relation_name,
            constraint_row.conname AS constraint_name,
            pg_get_constraintdef(constraint_row.oid) AS definition
        FROM pg_constraint AS constraint_row
        WHERE constraint_row.contype = 'f'
          AND constraint_row.confrelid IN (
              'workplans'::regclass,
              'tasks'::regclass
          )
    LOOP
        updated_definition := regexp_replace(
            fk.definition,
            ' ON UPDATE CASCADE',
            '',
            'i'
        );
        EXECUTE format(
            'ALTER TABLE %s DROP CONSTRAINT %I',
            fk.relation_name,
            fk.constraint_name
        );
        EXECUTE format(
            'ALTER TABLE %s ADD CONSTRAINT %I %s',
            fk.relation_name,
            fk.constraint_name,
            updated_definition
        );
    END LOOP;
END $$;
"""


def upgrade() -> None:
    op.create_table(
        "work_record_identifier_aliases",
        sa.Column("id", UUID(as_uuid=True), primary_key=True),
        sa.Column("old_id", UUID(as_uuid=True), nullable=False),
        sa.Column("new_id", UUID(as_uuid=True), nullable=False),
        sa.Column("record_kind", sa.String(length=20), nullable=False),
        sa.Column("record_id", sa.String(length=160), nullable=False),
        sa.Column("repo_slug", sa.String(length=100), nullable=False),
        sa.Column("namespace", sa.String(length=64), nullable=False),
        sa.Column("plan_sha256", sa.String(length=64), nullable=False),
        sa.Column(
            "migration_status",
            sa.String(length=20),
            nullable=False,
            server_default="prepared",
        ),
        sa.Column("applied_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("reversed_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=False,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=False,
        ),
        sa.CheckConstraint(
            "record_kind IN ('workplan', 'task')",
            name="ck_work_record_identifier_aliases_kind",
        ),
        sa.CheckConstraint(
            "migration_status IN ('prepared', 'applied', 'reversed')",
            name="ck_work_record_identifier_aliases_status",
        ),
        sa.UniqueConstraint("old_id", name="uq_work_record_identifier_aliases_old_id"),
        sa.UniqueConstraint("new_id", name="uq_work_record_identifier_aliases_new_id"),
    )
    op.create_index(
        "ix_work_record_identifier_aliases_record_id",
        "work_record_identifier_aliases",
        ["record_id"],
    )
    op.create_index(
        "ix_work_record_identifier_aliases_repo_slug",
        "work_record_identifier_aliases",
        ["repo_slug"],
    )
    op.create_index(
        "ix_work_record_identifier_aliases_plan_sha256",
        "work_record_identifier_aliases",
        ["plan_sha256"],
    )
    op.execute(_CASCADE_REFERENCES)


def downgrade() -> None:
    op.execute(_REMOVE_UPDATE_CASCADE)
    op.drop_index(
        "ix_work_record_identifier_aliases_plan_sha256",
        table_name="work_record_identifier_aliases",
    )
    op.drop_index(
        "ix_work_record_identifier_aliases_repo_slug",
        table_name="work_record_identifier_aliases",
    )
    op.drop_index(
        "ix_work_record_identifier_aliases_record_id",
        table_name="work_record_identifier_aliases",
    )
    op.drop_table("work_record_identifier_aliases")
