"""add runtime coordination and recovery evidence

Revision ID: e14b8c2d6f90
Revises: d03a7b9c1e5f
Create Date: 2026-08-01 00:00:00.000000
"""

from __future__ import annotations

from alembic import op
import sqlalchemy as sa


revision = "e14b8c2d6f90"
down_revision = "d03a7b9c1e5f"
branch_labels = None
depends_on = None


def upgrade() -> None:
    inspector = sa.inspect(op.get_bind())
    tables = set(inspector.get_table_names())
    if "core_runtime_nodes" not in tables:
        op.create_table(
            "core_runtime_nodes",
            sa.Column("id", sa.String(length=36), nullable=False),
            sa.Column("installation_id", sa.String(length=100), nullable=False),
            sa.Column("node_id", sa.String(length=200), nullable=False),
            sa.Column("incarnation", sa.String(length=36), nullable=False),
            sa.Column("role", sa.String(length=40), nullable=False),
            sa.Column("software_version", sa.String(length=80), nullable=False),
            sa.Column("composition_hash", sa.String(length=64), nullable=False),
            sa.Column("queues", sa.JSON(), nullable=False),
            sa.Column("state", sa.String(length=30), nullable=False),
            sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
            sa.Column("last_heartbeat_at", sa.DateTime(timezone=True), nullable=False),
            sa.Column("drain_requested_at", sa.DateTime(timezone=True), nullable=True),
            sa.Column("drain_reason", sa.String(length=500), nullable=True),
            sa.Column("stopped_at", sa.DateTime(timezone=True), nullable=True),
            sa.Column("metadata", sa.JSON(), nullable=False),
            sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
            sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
            sa.PrimaryKeyConstraint("id", name=op.f("pk_core_runtime_nodes")),
            sa.UniqueConstraint(
                "installation_id",
                "node_id",
                name="uq_core_runtime_node_installation_node",
            ),
        )
        for column in (
            "installation_id",
            "node_id",
            "incarnation",
            "role",
            "composition_hash",
            "state",
        ):
            op.create_index(
                op.f(f"ix_core_runtime_nodes_{column}"),
                "core_runtime_nodes",
                [column],
                unique=False,
            )
        op.create_index(
            "ix_core_runtime_nodes_installation_state_heartbeat",
            "core_runtime_nodes",
            ["installation_id", "state", "last_heartbeat_at"],
            unique=False,
        )

    if "core_distributed_leases" not in tables:
        op.create_table(
            "core_distributed_leases",
            sa.Column("id", sa.String(length=36), nullable=False),
            sa.Column("installation_id", sa.String(length=100), nullable=False),
            sa.Column("resource_key", sa.String(length=255), nullable=False),
            sa.Column("holder_node_id", sa.String(length=200), nullable=True),
            sa.Column("holder_incarnation", sa.String(length=36), nullable=True),
            sa.Column("fencing_token", sa.BigInteger(), nullable=False),
            sa.Column("acquired_at", sa.DateTime(timezone=True), nullable=True),
            sa.Column("renewed_at", sa.DateTime(timezone=True), nullable=True),
            sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
            sa.Column("metadata", sa.JSON(), nullable=False),
            sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
            sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
            sa.PrimaryKeyConstraint("id", name=op.f("pk_core_distributed_leases")),
            sa.UniqueConstraint(
                "installation_id",
                "resource_key",
                name="uq_core_distributed_lease_resource",
            ),
        )
        for column in (
            "installation_id",
            "resource_key",
            "holder_node_id",
            "holder_incarnation",
        ):
            op.create_index(
                op.f(f"ix_core_distributed_leases_{column}"),
                "core_distributed_leases",
                [column],
                unique=False,
            )
        op.create_index(
            "ix_core_distributed_leases_expiry",
            "core_distributed_leases",
            ["installation_id", "expires_at"],
            unique=False,
        )

    if "core_recovery_operations" not in tables:
        op.create_table(
            "core_recovery_operations",
            sa.Column("id", sa.String(length=36), nullable=False),
            sa.Column("installation_id", sa.String(length=100), nullable=False),
            sa.Column("module_id", sa.String(length=100), nullable=False),
            sa.Column("operation_type", sa.String(length=100), nullable=False),
            sa.Column("resource_type", sa.String(length=100), nullable=True),
            sa.Column("resource_id", sa.String(length=255), nullable=True),
            sa.Column("mode", sa.String(length=40), nullable=False),
            sa.Column("status", sa.String(length=40), nullable=False),
            sa.Column("idempotency_key", sa.String(length=200), nullable=False),
            sa.Column("request_sha256", sa.String(length=64), nullable=False),
            sa.Column("plan", sa.JSON(), nullable=False),
            sa.Column("backup_reference", sa.String(length=1000), nullable=True),
            sa.Column("approval_reference", sa.String(length=1000), nullable=True),
            sa.Column("lease_resource_key", sa.String(length=255), nullable=True),
            sa.Column("holder_node_id", sa.String(length=200), nullable=True),
            sa.Column("holder_incarnation", sa.String(length=36), nullable=True),
            sa.Column("fencing_token", sa.BigInteger(), nullable=True),
            sa.Column("checkpoint_count", sa.Integer(), nullable=False),
            sa.Column("evidence_head_sha256", sa.String(length=64), nullable=True),
            sa.Column("failure_summary", sa.Text(), nullable=True),
            sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
            sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True),
            sa.Column("recovery_started_at", sa.DateTime(timezone=True), nullable=True),
            sa.Column("recovered_at", sa.DateTime(timezone=True), nullable=True),
            sa.Column("revision", sa.Integer(), nullable=False),
            sa.Column("metadata", sa.JSON(), nullable=False),
            sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
            sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
            sa.PrimaryKeyConstraint("id", name=op.f("pk_core_recovery_operations")),
            sa.UniqueConstraint(
                "installation_id",
                "module_id",
                "idempotency_key",
                name="uq_core_recovery_operation_idempotency",
            ),
        )
        for column in (
            "installation_id",
            "module_id",
            "operation_type",
            "resource_type",
            "resource_id",
            "mode",
            "status",
        ):
            op.create_index(
                op.f(f"ix_core_recovery_operations_{column}"),
                "core_recovery_operations",
                [column],
                unique=False,
            )
        op.create_index(
            "ix_core_recovery_operations_status_updated",
            "core_recovery_operations",
            ["installation_id", "status", "updated_at"],
            unique=False,
        )
        op.create_index(
            "ix_core_recovery_operations_resource",
            "core_recovery_operations",
            ["module_id", "resource_type", "resource_id"],
            unique=False,
        )

    if "core_recovery_checkpoints" not in tables:
        op.create_table(
            "core_recovery_checkpoints",
            sa.Column("id", sa.String(length=36), nullable=False),
            sa.Column("operation_id", sa.String(length=36), nullable=False),
            sa.Column("sequence", sa.Integer(), nullable=False),
            sa.Column("status", sa.String(length=40), nullable=False),
            sa.Column("kind", sa.String(length=80), nullable=False),
            sa.Column("summary", sa.Text(), nullable=False),
            sa.Column("evidence", sa.JSON(), nullable=False),
            sa.Column("previous_sha256", sa.String(length=64), nullable=True),
            sa.Column("checkpoint_sha256", sa.String(length=64), nullable=False),
            sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
            sa.ForeignKeyConstraint(
                ["operation_id"],
                ["core_recovery_operations.id"],
                name=op.f(
                    "fk_core_recovery_checkpoints_operation_id_core_recovery_operations"
                ),
                ondelete="CASCADE",
            ),
            sa.PrimaryKeyConstraint("id", name=op.f("pk_core_recovery_checkpoints")),
            sa.UniqueConstraint(
                "operation_id",
                "sequence",
                name="uq_core_recovery_checkpoint_sequence",
            ),
        )
        for column in ("operation_id", "status", "checkpoint_sha256"):
            op.create_index(
                op.f(f"ix_core_recovery_checkpoints_{column}"),
                "core_recovery_checkpoints",
                [column],
                unique=False,
            )
        op.create_index(
            "ix_core_recovery_checkpoints_operation_created",
            "core_recovery_checkpoints",
            ["operation_id", "created_at"],
            unique=False,
        )


def downgrade() -> None:
    inspector = sa.inspect(op.get_bind())
    tables = set(inspector.get_table_names())
    for table in (
        "core_recovery_checkpoints",
        "core_recovery_operations",
        "core_distributed_leases",
        "core_runtime_nodes",
    ):
        if table in tables:
            op.drop_table(table)
