"""One-time reverse migration for the paper-book tables: drop the vestigial, single-valued `venue`
discriminator column. bybit is fxhnt's sole paper book (Task 7f hardcoded every `PaperRepo` read/write to it),
and the prod purge left every table bybit-only — so the new venue-less primary keys are collision-free.

`PaperRepo.migrate()` calls this on every cockpit-pod startup (git-sync deploy), so it MUST be idempotent and a
pure NO-OP after the first successful run: heavy DDL on every startup crashloops the 4Gi dashboard. Each table's
work is guarded on the live `venue` column still being present, so once dropped the whole pass short-circuits.

Postgres-only: a fresh `create_all` already emits the venue-less schema (new deployments / the sqlite test DBs),
and sqlite cannot ALTER a primary key. This module is the sole home of the column-drop DDL, keeping `paper_repo`
free of any discriminator logic."""
from __future__ import annotations

from sqlalchemy import Connection, Engine, Inspector, inspect, text

# The five composite-PK paper tables that shed `venue` from their primary key → the venue-less PK column set.
_PK_TABLES: dict[str, tuple[str, ...]] = {
    "paper_positions": ("run_date", "sleeve", "symbol"),
    "paper_nav": ("run_date",),
    "paper_book_state": ("key",),
    "paper_sleeve_ret": ("run_date", "sleeve"),
    "paper_shadow_positions": ("run_date", "sleeve", "symbol"),
}


def _drop_pk_and_column(c: Connection, insp: Inspector, tbl: str, new_pk: tuple[str, ...]) -> None:
    """Drop `tbl`'s existing PK, drop its `venue` column, and re-add the PRIMARY KEY on `new_pk`. No-op when the
    column is already absent (already migrated), which keeps the whole pass a no-op after the first run."""
    if not insp.has_table(tbl):
        return
    cols = {col["name"] for col in insp.get_columns(tbl)}
    if "venue" not in cols:
        return
    pk_name = insp.get_pk_constraint(tbl).get("name") or f"{tbl}_pkey"
    c.execute(text(f'ALTER TABLE {tbl} DROP CONSTRAINT "{pk_name}"'))
    c.execute(text(f"ALTER TABLE {tbl} DROP COLUMN venue"))
    c.execute(text(f'ALTER TABLE {tbl} ADD PRIMARY KEY ({", ".join(new_pk)})'))


def migrate_paper_book_schema(engine: Engine) -> None:
    """Reverse-migrate the paper-book tables to the venue-less schema (see module docstring). Assumes
    `create_all` has already run (fresh tables are created venue-less; this only rewrites pre-existing ones)."""
    if engine.dialect.name == "sqlite":
        # Fresh sqlite `create_all` already has the venue-less schema, and sqlite can't ALTER a PK. The
        # newest-first trades read wants the (ts, id) index either way.
        with engine.begin() as c:
            c.execute(text("CREATE INDEX IF NOT EXISTS ix_paper_trades_ts_id ON paper_trades (ts, id)"))
        return

    insp = inspect(engine)
    with engine.begin() as c:
        for tbl, new_pk in _PK_TABLES.items():
            _drop_pk_and_column(c, insp, tbl, new_pk)

        # paper_nav_summary: its SOLE PK was `venue` (one materialized row). Replace it with a singleton
        # surrogate `id` defaulted to 1 for the existing bybit row, then PK on id.
        if insp.has_table("paper_nav_summary"):
            cols = {col["name"] for col in insp.get_columns("paper_nav_summary")}
            if "venue" in cols:
                pk_name = insp.get_pk_constraint("paper_nav_summary").get("name") or "paper_nav_summary_pkey"
                c.execute(text(f'ALTER TABLE paper_nav_summary DROP CONSTRAINT "{pk_name}"'))
                c.execute(text("ALTER TABLE paper_nav_summary ADD COLUMN id INTEGER NOT NULL DEFAULT 1"))
                c.execute(text("ALTER TABLE paper_nav_summary DROP COLUMN venue"))
                c.execute(text("ALTER TABLE paper_nav_summary ADD PRIMARY KEY (id)"))

        # paper_trades: `venue` was a non-PK indexed column → drop its index then the column (the PK is the
        # autoincrement id, untouched). The newest-first read no longer filters on it.
        if insp.has_table("paper_trades"):
            cols = {col["name"] for col in insp.get_columns("paper_trades")}
            if "venue" in cols:
                c.execute(text("DROP INDEX IF EXISTS ix_paper_trades_venue_ts_id"))
                c.execute(text("ALTER TABLE paper_trades DROP COLUMN venue"))

        # The newest-first trades read backward-scans (ts, id) for the top-N (no venue filter any more).
        # IF NOT EXISTS → idempotent. Replaces the old (venue, ts, id) index dropped just above.
        c.execute(text("CREATE INDEX IF NOT EXISTS ix_paper_trades_ts_id ON paper_trades (ts, id)"))
