"""Add stable artifact identity and lifecycle metadata.

Revision ID: 0028
Revises: 0027
Create Date: 2026-08-12
"""
from typing import Sequence, Union

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


revision: str = "0028"
down_revision: Union[str, None] = "0027"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    op.create_table(
        "artifacts",
        sa.Column(
            "artifact_id",
            postgresql.UUID(as_uuid=True),
            nullable=False,
        ),
        sa.Column(
            "user_id",
            postgresql.UUID(as_uuid=True),
            nullable=False,
        ),
        sa.Column(
            "origin_task_id",
            postgresql.UUID(as_uuid=True),
            nullable=True,
        ),
        sa.Column(
            "copied_from_artifact_id",
            postgresql.UUID(as_uuid=True),
            nullable=True,
        ),
        sa.Column("current_path", sa.Text(), nullable=False),
        sa.Column("label", sa.Text(), server_default="", nullable=False),
        sa.Column("media_type", sa.Text(), nullable=True),
        sa.Column("size_bytes", sa.BigInteger(), nullable=True),
        sa.Column("content_sha256", sa.Text(), nullable=True),
        sa.Column("status", sa.Text(), server_default="active", nullable=False),
        sa.Column("trash_path", sa.Text(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.func.now(),
            nullable=False,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=sa.func.now(),
            nullable=False,
        ),
        sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
        sa.CheckConstraint(
            "status IN ('active', 'deleted')",
            name="ck_artifacts_status",
        ),
        sa.ForeignKeyConstraint(["user_id"], ["users.user_id"]),
        sa.ForeignKeyConstraint(
            ["origin_task_id"],
            ["tasks.task_id"],
            ondelete="SET NULL",
        ),
        sa.ForeignKeyConstraint(
            ["copied_from_artifact_id"],
            ["artifacts.artifact_id"],
            ondelete="SET NULL",
        ),
        sa.PrimaryKeyConstraint("artifact_id"),
    )
    op.create_index(
        "ix_artifacts_user_status_path",
        "artifacts",
        ["user_id", "status", "current_path"],
    )
    op.create_index(
        "ix_artifacts_origin_task",
        "artifacts",
        ["origin_task_id"],
    )
    op.create_index(
        "uq_artifacts_active_user_path",
        "artifacts",
        ["user_id", "current_path"],
        unique=True,
        postgresql_where=sa.text("status = 'active'"),
    )
    # Backfill legacy structured refs without rewriting append-only message metadata.
    # working_dir is stored as workspace/users/<uuid>/<user-relative-dir>.
    op.execute(
        r"""
        INSERT INTO artifacts (
            artifact_id, user_id, origin_task_id, current_path, label, status
        )
        SELECT DISTINCT ON (t.user_id, current_path)
            gen_random_uuid(),
            t.user_id,
            t.task_id,
            current_path,
            COALESCE(ref->>'label', ''),
            'active'
        FROM tasks AS t
        JOIN messages AS m ON m.task_id = t.task_id
        CROSS JOIN LATERAL jsonb_array_elements(
            CASE
                WHEN jsonb_typeof(m.artifact_refs) = 'array'
                THEN m.artifact_refs
                ELSE '[]'::jsonb
            END
        ) AS ref
        CROSS JOIN LATERAL (
            SELECT
                regexp_replace(
                    t.working_dir,
                    '^workspace/users/' || t.user_id::text || '/',
                    ''
                ) || '/' || (ref->>'path') AS current_path
        ) AS resolved
        WHERE m.artifact_refs IS NOT NULL
          AND ref->>'scope' = 'working_dir'
          AND COALESCE(ref->>'path', '') <> ''
          AND ref->>'path' !~ '(^/|(^|/)\.\.(/|$))'
          AND strpos(ref->>'path', chr(92)) = 0
        ORDER BY t.user_id, current_path, m.created_at
        ON CONFLICT DO NOTHING
        """
    )


def downgrade() -> None:
    op.drop_index("uq_artifacts_active_user_path", table_name="artifacts")
    op.drop_index("ix_artifacts_origin_task", table_name="artifacts")
    op.drop_index("ix_artifacts_user_status_path", table_name="artifacts")
    op.drop_table("artifacts")
