"""Add timeline_events, changed_files, project workspace pointers, extended enums.

Revision ID: 004
Revises: 003
Create Date: 2025-03-10

Supports project-rooted builder workspace:
- timeline_events: normalized execution timeline
- changed_files: durable changed-file metadata per run/revision
- projects: active_revision_id, active_preview_id, active_session_id
- Extended project and codegen status enums
"""
from typing import Sequence, Union

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

revision: str = "004"
down_revision: Union[str, None] = "003"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    # Extend project status enum (add generating, build_pending, preview_pending, publishing)
    op.execute("ALTER TYPE p2hs_project_status ADD VALUE IF NOT EXISTS 'generating'")
    op.execute("ALTER TYPE p2hs_project_status ADD VALUE IF NOT EXISTS 'build_pending'")
    op.execute("ALTER TYPE p2hs_project_status ADD VALUE IF NOT EXISTS 'preview_pending'")
    op.execute("ALTER TYPE p2hs_project_status ADD VALUE IF NOT EXISTS 'publishing'")

    # Extend codegen run status enum (add queued, validating, preview_refreshing, canceled)
    op.execute("ALTER TYPE p2hs_codegen_run_status ADD VALUE IF NOT EXISTS 'queued'")
    op.execute("ALTER TYPE p2hs_codegen_run_status ADD VALUE IF NOT EXISTS 'validating'")
    op.execute("ALTER TYPE p2hs_codegen_run_status ADD VALUE IF NOT EXISTS 'preview_refreshing'")
    op.execute("ALTER TYPE p2hs_codegen_run_status ADD VALUE IF NOT EXISTS 'canceled'")

    # Add project workspace pointers (active revision, preview, session)
    op.add_column(
        "projects",
        sa.Column("active_revision_id", postgresql.UUID(as_uuid=True), nullable=True),
    )
    op.add_column(
        "projects",
        sa.Column("active_preview_id", postgresql.UUID(as_uuid=True), nullable=True),
    )
    op.add_column(
        "projects",
        sa.Column("active_session_id", postgresql.UUID(as_uuid=True), nullable=True),
    )
    op.create_foreign_key(
        "fk_projects_active_revision",
        "projects",
        "project_revisions",
        ["active_revision_id"],
        ["id"],
        ondelete="SET NULL",
    )
    op.create_foreign_key(
        "fk_projects_active_preview",
        "projects",
        "preview_environments",
        ["active_preview_id"],
        ["id"],
        ondelete="SET NULL",
    )
    op.create_foreign_key(
        "fk_projects_active_session",
        "projects",
        "prompt_sessions",
        ["active_session_id"],
        ["id"],
        ondelete="SET NULL",
    )

    # Timeline events - normalized execution timeline
    op.create_table(
        "timeline_events",
        sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
        sa.Column("project_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("projects.id", ondelete="CASCADE"), nullable=False),
        sa.Column("run_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("codegen_runs.id", ondelete="CASCADE")),
        sa.Column("revision_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("project_revisions.id", ondelete="SET NULL")),
        sa.Column("preview_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("preview_environments.id", ondelete="SET NULL")),
        sa.Column("deploy_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("project_deploys.id", ondelete="SET NULL")),
        sa.Column("event_type", sa.String(80), nullable=False),
        sa.Column("phase", sa.String(50)),
        sa.Column("title", sa.String(255)),
        sa.Column("detail_text", sa.Text()),
        sa.Column("payload", postgresql.JSONB()),
        sa.Column("sequence_no", sa.Integer(), nullable=False, server_default="0"),
        sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.func.now()),
    )
    op.create_index("idx_timeline_events_project_run", "timeline_events", ["project_id", "run_id", "sequence_no"])
    op.create_index("idx_timeline_events_project_created", "timeline_events", ["project_id", "created_at"])

    # Changed files - durable changed-file metadata per run/revision
    op.create_table(
        "changed_files",
        sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
        sa.Column("project_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("projects.id", ondelete="CASCADE"), nullable=False),
        sa.Column("run_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("codegen_runs.id", ondelete="CASCADE")),
        sa.Column("revision_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("project_revisions.id", ondelete="SET NULL")),
        sa.Column("file_path", sa.String(1024), nullable=False),
        sa.Column("change_type", sa.String(20), nullable=False),
        sa.Column("language", sa.String(50)),
        sa.Column("summary", sa.Text()),
        sa.Column("lines_added", sa.Integer()),
        sa.Column("lines_removed", sa.Integer()),
        sa.Column("patch_excerpt", sa.Text()),
        sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.func.now()),
    )
    op.create_index("idx_changed_files_project_run", "changed_files", ["project_id", "run_id"])
    op.create_index("idx_changed_files_project_revision", "changed_files", ["project_id", "revision_id"])


def downgrade() -> None:
    op.drop_index("idx_changed_files_project_revision", table_name="changed_files")
    op.drop_index("idx_changed_files_project_run", table_name="changed_files")
    op.drop_table("changed_files")

    op.drop_index("idx_timeline_events_project_created", table_name="timeline_events")
    op.drop_index("idx_timeline_events_project_run", table_name="timeline_events")
    op.drop_table("timeline_events")

    op.drop_constraint("fk_projects_active_session", "projects", type_="foreignkey")
    op.drop_constraint("fk_projects_active_preview", "projects", type_="foreignkey")
    op.drop_constraint("fk_projects_active_revision", "projects", type_="foreignkey")
    op.drop_column("projects", "active_session_id")
    op.drop_column("projects", "active_preview_id")
    op.drop_column("projects", "active_revision_id")

    # Note: enum value removal is not straightforward in PostgreSQL; leave enum values
    # as-is in downgrade - they are backward compatible.
