"""Phase 3 tables: llm_call_cache, review_items, event_labels.

Revision ID: 0002
Revises: 0001
Create Date: 2026-03-12

Tables created:
- llm_call_cache: DB-backed cache for LLM API calls
- review_items: human-in-the-loop review queue
- event_labels: forward-return labels for parsed events
"""
from __future__ import annotations

from collections.abc import Sequence

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects.postgresql import JSONB, UUID

revision: str = "0002"
down_revision: str | None = "0001"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
    op.create_table(
        "llm_call_cache",
        sa.Column("cache_key", sa.Text, primary_key=True),
        sa.Column("document_id", sa.Text, nullable=True),
        sa.Column("model_name", sa.Text, nullable=False),
        sa.Column("prompt_version", sa.Text, nullable=False),
        sa.Column("schema_version", sa.Text, nullable=False),
        sa.Column("raw_prompt", sa.Text, nullable=False),
        sa.Column("raw_response", sa.Text, nullable=False),
        sa.Column("normalized_json", JSONB, nullable=False),
        sa.Column("token_usage_json", JSONB, nullable=False),
        sa.Column("elapsed_ms", sa.Integer, nullable=False),
        sa.Column(
            "created_at_utc",
            sa.DateTime(timezone=True),
            nullable=False,
            server_default=sa.text("now()"),
        ),
    )

    op.create_table(
        "review_items",
        sa.Column("review_id", UUID(as_uuid=True), primary_key=True),
        sa.Column("entity_type", sa.Text, nullable=False),
        sa.Column("entity_id", sa.Text, nullable=False),
        sa.Column("priority", sa.Text, nullable=False),
        sa.Column("reason_codes", JSONB, nullable=False),
        sa.Column("status", sa.Text, nullable=False, server_default=sa.text("'open'")),
        sa.Column("assigned_to", sa.Text, nullable=True),
        sa.Column("snapshot_refs", JSONB, nullable=False, server_default=sa.text("'{}'")),
        sa.Column("suggested_overrides", JSONB, nullable=True),
        sa.Column("reviewer_id", sa.Text, nullable=True),
        sa.Column("resolution_type", sa.Text, nullable=True),
        sa.Column("field_overrides", JSONB, nullable=True),
        sa.Column("root_cause", sa.Text, nullable=True),
        sa.Column("notes", sa.Text, nullable=True),
        sa.Column("resolved_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column(
            "created_at_utc",
            sa.DateTime(timezone=True),
            nullable=False,
            server_default=sa.text("now()"),
        ),
        sa.Column(
            "updated_at_utc",
            sa.DateTime(timezone=True),
            nullable=False,
            server_default=sa.text("now()"),
        ),
    )
    op.create_index(
        "ix_review_items_entity_open", "review_items", ["entity_type", "entity_id", "status"]
    )
    op.create_index(
        "ix_review_items_status_priority", "review_items", ["status", "priority"]
    )
    # Partial unique index: only one open review per entity
    op.create_index(
        "uix_review_items_entity_open",
        "review_items",
        ["entity_type", "entity_id"],
        unique=True,
        postgresql_where=sa.text("status = 'open'"),
    )

    op.create_table(
        "event_labels",
        sa.Column("label_id", sa.BigInteger, primary_key=True, autoincrement=True),
        sa.Column(
            "event_id",
            sa.Text,
            sa.ForeignKey("events.event_id"),
            nullable=False,
        ),
        sa.Column("entry_convention", sa.Text, nullable=False),
        sa.Column("reaction_date", sa.Date, nullable=True),
        sa.Column("entry_date", sa.Date, nullable=True),
        sa.Column("entry_price", sa.Numeric, nullable=True),
        sa.Column("fwd_return_1d", sa.Numeric, nullable=True),
        sa.Column("fwd_return_3d", sa.Numeric, nullable=True),
        sa.Column("fwd_return_5d", sa.Numeric, nullable=True),
        sa.Column("hit_pos_1r_within_3d", sa.Boolean, nullable=True),
        sa.Column("hit_neg_1r_within_3d", sa.Boolean, nullable=True),
        sa.Column("close_up_after_3d", sa.Boolean, nullable=True),
        sa.Column("close_up_after_5d", sa.Boolean, nullable=True),
        sa.Column("mfe_3d", sa.Numeric, nullable=True),
        sa.Column("mae_3d", sa.Numeric, nullable=True),
        sa.Column("mfe_5d", sa.Numeric, nullable=True),
        sa.Column("mae_5d", sa.Numeric, nullable=True),
        sa.Column("bars_to_mfe_3d", sa.Integer, nullable=True),
        sa.Column("bars_to_mae_3d", sa.Integer, nullable=True),
        sa.Column("days_to_peak_close_5d", sa.Integer, nullable=True),
        sa.Column("label_status", sa.Text, nullable=False),
        sa.Column(
            "invalid_event_for_labeling",
            sa.Boolean,
            nullable=False,
            server_default=sa.text("false"),
        ),
        sa.Column("risk_model_name", sa.Text, nullable=True),
        sa.Column("initial_stop_price", sa.Numeric, nullable=True),
        sa.Column("initial_r_value", sa.Numeric, nullable=True),
        sa.Column("label_version", sa.Text, nullable=False),
        sa.Column(
            "created_at_utc",
            sa.DateTime(timezone=True),
            nullable=False,
            server_default=sa.text("now()"),
        ),
        sa.UniqueConstraint(
            "event_id",
            "entry_convention",
            "label_version",
            name="uq_event_labels_event_convention_version",
        ),
    )
    op.create_index("ix_event_labels_event_id", "event_labels", ["event_id"])


def downgrade() -> None:
    op.drop_index("ix_event_labels_event_id", table_name="event_labels")
    op.drop_table("event_labels")
    op.drop_index("uix_review_items_entity_open", table_name="review_items")
    op.drop_index("ix_review_items_status_priority", table_name="review_items")
    op.drop_index("ix_review_items_entity_open", table_name="review_items")
    op.drop_table("review_items")
    op.drop_table("llm_call_cache")
