"""Replace actions_v3 with spec-aligned actions table.

This migration drops the old actions_v3 table (ULID PK) and creates a new
actions table using the namespaced_name as the primary key per spec.  It also
creates the action_invariants and action_arguments child tables.

The lifecycle_plans table is dropped first (FK dependency) and recreated by
a5_004.

Revision ID: a5_003_spec_aligned_actions
Revises: a5_002_lifecycle_plans
Create Date: 2026-02-13 00:00:00

"""

from collections.abc import Sequence

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "a5_003_spec_aligned_actions"
down_revision: str | Sequence[str] | None = "a5_002_lifecycle_plans"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
    """Drop legacy tables and create spec-aligned actions + child tables."""
    # --- Drop legacy tables (FK order: lifecycle_plans first, then actions_v3) ---
    op.drop_index("ix_lifecycle_plans_action", table_name="lifecycle_plans")
    op.drop_index("ix_lifecycle_plans_created", table_name="lifecycle_plans")
    op.drop_index("ix_lifecycle_plans_root", table_name="lifecycle_plans")
    op.drop_index("ix_lifecycle_plans_parent", table_name="lifecycle_plans")
    op.drop_index("ix_lifecycle_plans_state", table_name="lifecycle_plans")
    op.drop_index("ix_lifecycle_plans_phase", table_name="lifecycle_plans")
    op.drop_table("lifecycle_plans")

    op.drop_index("ix_actions_v3_short_name", table_name="actions_v3")
    op.drop_index("ix_actions_v3_state", table_name="actions_v3")
    op.drop_index("ix_actions_v3_namespace", table_name="actions_v3")
    op.drop_table("actions_v3")

    # --- Create spec-aligned actions table (namespaced_name PK) ---
    op.create_table(
        "actions",
        # Primary key: the full namespaced name (e.g., "local/code-review")
        sa.Column("namespaced_name", sa.String(255), nullable=False),
        # Extracted components for filtering
        sa.Column("namespace", sa.String(100), nullable=False),
        sa.Column("name", sa.String(150), nullable=False),
        # Descriptions
        sa.Column("description", sa.Text(), nullable=False),
        sa.Column("long_description", sa.Text(), nullable=True),
        # Definition of done - explicit testable completion criteria
        sa.Column("definition_of_done", sa.Text(), nullable=False),
        # Actor references (namespaced names)
        sa.Column("strategy_actor", sa.String(255), nullable=False),
        sa.Column("execution_actor", sa.String(255), nullable=False),
        sa.Column("review_actor", sa.String(255), nullable=True),
        sa.Column("apply_actor", sa.String(255), nullable=True),
        sa.Column("estimation_actor", sa.String(255), nullable=True),
        sa.Column("invariant_actor", sa.String(255), nullable=True),
        # Behavior flags
        sa.Column("automation_profile", sa.String(255), nullable=True),
        sa.Column(
            "reusable",
            sa.Boolean(),
            nullable=False,
            server_default=sa.text("1"),
        ),
        sa.Column(
            "read_only",
            sa.Boolean(),
            nullable=False,
            server_default=sa.text("0"),
        ),
        # Inputs schema - JSON Schema dict for validation
        sa.Column("inputs_schema_json", sa.Text(), nullable=True),
        # State management
        sa.Column(
            "state",
            sa.String(20),
            nullable=False,
            server_default="available",
        ),
        # Metadata
        sa.Column("created_by", sa.String(255), nullable=True),
        sa.Column("tags_json", sa.Text(), nullable=False, server_default="[]"),
        # Timestamps (ISO-8601 strings for portability)
        sa.Column("created_at", sa.String(30), nullable=False),
        sa.Column("updated_at", sa.String(30), nullable=False),
        # Constraints
        sa.PrimaryKeyConstraint("namespaced_name"),
        sa.CheckConstraint(
            "state IN ('available', 'archived')",
            name="ck_actions_state",
        ),
    )

    # Indices for common queries
    op.create_index("ix_actions_namespace", "actions", ["namespace"], unique=False)
    op.create_index("ix_actions_state", "actions", ["state"], unique=False)
    op.create_index("ix_actions_name", "actions", ["name"], unique=False)

    # --- Create action_invariants table ---
    op.create_table(
        "action_invariants",
        sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
        sa.Column(
            "action_name",
            sa.String(255),
            sa.ForeignKey(
                "actions.namespaced_name",
                ondelete="CASCADE",
                name="fk_action_invariants_action",
            ),
            nullable=False,
        ),
        sa.Column("invariant_text", sa.Text(), nullable=False),
        sa.Column("position", sa.Integer(), nullable=False),
        sa.Column("created_at", sa.String(30), nullable=False),
        sa.PrimaryKeyConstraint("id"),
    )

    op.create_index(
        "ix_action_invariants_action",
        "action_invariants",
        ["action_name"],
        unique=False,
    )
    op.create_index(
        "ix_action_invariants_position",
        "action_invariants",
        ["action_name", "position"],
        unique=False,
    )

    # --- Create action_arguments table ---
    op.create_table(
        "action_arguments",
        sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
        sa.Column(
            "action_name",
            sa.String(255),
            sa.ForeignKey(
                "actions.namespaced_name",
                ondelete="CASCADE",
                name="fk_action_arguments_action",
            ),
            nullable=False,
        ),
        sa.Column("name", sa.String(100), nullable=False),
        sa.Column(
            "arg_type",
            sa.String(20),
            nullable=False,
            server_default="string",
        ),
        sa.Column(
            "requirement",
            sa.String(20),
            nullable=False,
            server_default="required",
        ),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column("default_value_json", sa.Text(), nullable=True),
        sa.Column("min_value", sa.Float(), nullable=True),
        sa.Column("max_value", sa.Float(), nullable=True),
        sa.Column("validation_pattern", sa.String(500), nullable=True),
        sa.Column("position", sa.Integer(), nullable=False),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("action_name", "name", name="uq_action_arguments_name"),
        sa.CheckConstraint(
            "arg_type IN ('string', 'integer', 'float', 'boolean', 'list')",
            name="ck_action_arguments_type",
        ),
        sa.CheckConstraint(
            "requirement IN ('required', 'optional')",
            name="ck_action_arguments_requirement",
        ),
    )

    op.create_index(
        "ix_action_arguments_action",
        "action_arguments",
        ["action_name"],
        unique=False,
    )
    op.create_index(
        "ix_action_arguments_position",
        "action_arguments",
        ["action_name", "position"],
        unique=False,
    )


def downgrade() -> None:
    """Drop spec-aligned tables and restore legacy tables."""
    # Drop new tables
    op.drop_index("ix_action_arguments_position", table_name="action_arguments")
    op.drop_index("ix_action_arguments_action", table_name="action_arguments")
    op.drop_table("action_arguments")

    op.drop_index("ix_action_invariants_position", table_name="action_invariants")
    op.drop_index("ix_action_invariants_action", table_name="action_invariants")
    op.drop_table("action_invariants")

    op.drop_index("ix_actions_name", table_name="actions")
    op.drop_index("ix_actions_state", table_name="actions")
    op.drop_index("ix_actions_namespace", table_name="actions")
    op.drop_table("actions")

    # Restore legacy actions_v3 table
    op.create_table(
        "actions_v3",
        sa.Column("action_id", sa.String(26), nullable=False),
        sa.Column("name", sa.String(255), nullable=False),
        sa.Column("namespace", sa.String(100), nullable=False),
        sa.Column("short_name", sa.String(150), nullable=False),
        sa.Column("short_description", sa.Text(), nullable=True),
        sa.Column("long_description", sa.Text(), nullable=True),
        sa.Column("definition_of_done", sa.Text(), nullable=False),
        sa.Column("strategy_actor", sa.String(255), nullable=False),
        sa.Column("execution_actor", sa.String(255), nullable=False),
        sa.Column("estimation_actor", sa.String(255), nullable=True),
        sa.Column("review_actor", sa.String(255), nullable=True),
        sa.Column("apply_actor", sa.String(255), nullable=True),
        sa.Column("inputs_schema", sa.Text(), nullable=False, server_default="[]"),
        sa.Column("state", sa.String(20), nullable=False, server_default="draft"),
        sa.Column(
            "reusable", sa.Boolean(), nullable=False, server_default=sa.text("1")
        ),
        sa.Column(
            "read_only", sa.Boolean(), nullable=False, server_default=sa.text("0")
        ),
        sa.Column("created_by", sa.String(255), nullable=True),
        sa.Column("tags", sa.Text(), nullable=False, server_default="[]"),
        sa.Column("created_at", sa.String(30), nullable=False),
        sa.Column("updated_at", sa.String(30), nullable=False),
        sa.PrimaryKeyConstraint("action_id"),
        sa.UniqueConstraint("name", name="uq_actions_v3_name"),
        sa.CheckConstraint(
            "state IN ('available', 'draft', 'archived')",
            name="ck_actions_v3_state",
        ),
    )
    op.create_index(
        "ix_actions_v3_namespace", "actions_v3", ["namespace"], unique=False
    )
    op.create_index("ix_actions_v3_state", "actions_v3", ["state"], unique=False)
    op.create_index(
        "ix_actions_v3_short_name", "actions_v3", ["short_name"], unique=False
    )

    # Restore legacy lifecycle_plans table
    op.create_table(
        "lifecycle_plans",
        sa.Column("plan_id", sa.String(26), nullable=False),
        sa.Column("parent_plan_id", sa.String(26), nullable=True),
        sa.Column("root_plan_id", sa.String(26), nullable=True),
        sa.Column("action_id", sa.String(26), nullable=False),
        sa.Column("phase", sa.String(20), nullable=False),
        sa.Column("state", sa.String(20), nullable=False),
        sa.Column("attempt", sa.Integer(), nullable=False, server_default=sa.text("1")),
        sa.Column(
            "automation_level", sa.String(30), nullable=False, server_default="manual"
        ),
        sa.Column("name", sa.String(255), nullable=False),
        sa.Column("namespace", sa.String(100), nullable=False),
        sa.Column("short_name", sa.String(150), nullable=False),
        sa.Column("description", sa.Text(), nullable=False),
        sa.Column("definition_of_done", sa.Text(), nullable=True),
        sa.Column("strategy_actor", sa.String(255), nullable=True),
        sa.Column("execution_actor", sa.String(255), nullable=True),
        sa.Column("project_ids", sa.Text(), nullable=False, server_default="[]"),
        sa.Column("arguments", sa.Text(), nullable=True),
        sa.Column("strategy_context", sa.Text(), nullable=True),
        sa.Column("execution_log", sa.Text(), nullable=True),
        sa.Column("changeset_id", sa.String(26), nullable=True),
        sa.Column("sandbox_refs", sa.Text(), nullable=True),
        sa.Column("error_message", sa.Text(), nullable=True),
        sa.Column("error_details", sa.Text(), nullable=True),
        sa.Column("created_by", sa.String(255), nullable=True),
        sa.Column("tags", sa.Text(), nullable=False, server_default="[]"),
        sa.Column(
            "reusable", sa.Boolean(), nullable=False, server_default=sa.text("1")
        ),
        sa.Column(
            "read_only", sa.Boolean(), nullable=False, server_default=sa.text("0")
        ),
        sa.Column("created_at", sa.String(30), nullable=False),
        sa.Column("updated_at", sa.String(30), nullable=False),
        sa.Column("strategize_started_at", sa.String(30), nullable=True),
        sa.Column("strategize_completed_at", sa.String(30), nullable=True),
        sa.Column("execute_started_at", sa.String(30), nullable=True),
        sa.Column("execute_completed_at", sa.String(30), nullable=True),
        sa.Column("apply_started_at", sa.String(30), nullable=True),
        sa.Column("applied_at", sa.String(30), nullable=True),
        sa.PrimaryKeyConstraint("plan_id"),
        sa.ForeignKeyConstraint(
            ["parent_plan_id"],
            ["lifecycle_plans.plan_id"],
            ondelete="SET NULL",
            name="fk_lifecycle_plans_parent",
        ),
        sa.ForeignKeyConstraint(
            ["root_plan_id"],
            ["lifecycle_plans.plan_id"],
            ondelete="SET NULL",
            name="fk_lifecycle_plans_root",
        ),
        sa.ForeignKeyConstraint(
            ["action_id"],
            ["actions_v3.action_id"],
            ondelete="RESTRICT",
            name="fk_lifecycle_plans_action",
        ),
        sa.CheckConstraint(
            "phase IN ('action', 'strategize', 'execute', 'apply', 'applied')",
            name="ck_lifecycle_plans_phase",
        ),
        sa.CheckConstraint(
            "state IN ('available', 'draft', 'archived', "
            "'queued', 'processing', 'errored', 'complete', 'cancelled')",
            name="ck_lifecycle_plans_state",
        ),
        sa.CheckConstraint(
            "automation_level IN ('manual', 'review_before_apply', 'full_automation')",
            name="ck_lifecycle_plans_automation",
        ),
    )
    op.create_index(
        "ix_lifecycle_plans_phase", "lifecycle_plans", ["phase"], unique=False
    )
    op.create_index(
        "ix_lifecycle_plans_state", "lifecycle_plans", ["state"], unique=False
    )
    op.create_index(
        "ix_lifecycle_plans_parent", "lifecycle_plans", ["parent_plan_id"], unique=False
    )
    op.create_index(
        "ix_lifecycle_plans_root", "lifecycle_plans", ["root_plan_id"], unique=False
    )
    op.create_index(
        "ix_lifecycle_plans_created", "lifecycle_plans", ["created_at"], unique=False
    )
    op.create_index(
        "ix_lifecycle_plans_action", "lifecycle_plans", ["action_id"], unique=False
    )
