"""Add ldr_version and settings_snapshot columns to benchmark_runs.

Background
==========
The YAML downloaded from a benchmark run is currently stamped with the
*current* app version and *current* date at download time, not the values
that were active when the benchmark actually ran (see
``benchmark_results.html:593-596`` — ``new Date()`` and the
``meta[name="app-version"]`` tag). A v1.6.5 run downloaded on v1.6.7
incorrectly shows v1.6.7. Reproducibility breaks because cross-run
comparisons can't trust the stamped version. Additionally, only ~3
settings (temperature, context_window, max_tokens) make it into the
YAML — the ~30-50 other settings that affect benchmark behaviour
(``search.iterations``, ``search.questions_per_iteration``,
``search.fetch.mode``, etc.) are lost.

This migration adds two nullable columns to ``benchmark_runs``:

- ``ldr_version`` (String(32)) — the LDR version active when the
  benchmark started, captured at ``start_benchmark`` time.
- ``settings_snapshot`` (JSON) — the redacted full settings snapshot
  (``DataSanitizer.redact_settings_snapshot`` strips API keys),
  preserving the per-key metadata so downstream consumers can render
  human-readable diffs.

Existing rows
=============
Both columns are nullable. Pre-0014 rows get ``NULL`` and the YAML
download path treats this as "pre-0014 run" (UI shows
``ldr_version: unknown (pre-0014 run)`` and a ``# settings: not
recorded`` comment). No backfill — there's no source of truth for what
those rows actually ran with.

Idempotent
==========
The upgrade and downgrade both inspect the table and column state before
mutating, so re-running the migration after a partial failure is safe.

Revision ID: 0014
Revises: 0013
Create Date: 2026-05-03
"""

from __future__ import annotations

import sqlalchemy as sa
from alembic import op
from loguru import logger
from sqlalchemy import inspect

revision = "0014"
down_revision = "0013"
branch_labels = None
depends_on = None


def _column_exists(table: str, column: str) -> bool:
    """Check whether ``column`` exists on ``table`` in the current DB."""
    insp = inspect(op.get_bind())
    if not insp.has_table(table):
        return False
    return column in {c["name"] for c in insp.get_columns(table)}


def upgrade() -> None:
    if not inspect(op.get_bind()).has_table("benchmark_runs"):
        return

    added: list[str] = []
    with op.batch_alter_table("benchmark_runs") as batch_op:
        if not _column_exists("benchmark_runs", "ldr_version"):
            batch_op.add_column(
                sa.Column("ldr_version", sa.String(32), nullable=True)
            )
            added.append("ldr_version")
        if not _column_exists("benchmark_runs", "settings_snapshot"):
            batch_op.add_column(
                sa.Column("settings_snapshot", sa.JSON, nullable=True)
            )
            added.append("settings_snapshot")

    if added:
        logger.info("0014: added benchmark_runs columns: {}", ", ".join(added))


def downgrade() -> None:
    if not inspect(op.get_bind()).has_table("benchmark_runs"):
        return

    with op.batch_alter_table("benchmark_runs") as batch_op:
        if _column_exists("benchmark_runs", "settings_snapshot"):
            batch_op.drop_column("settings_snapshot")
        if _column_exists("benchmark_runs", "ldr_version"):
            batch_op.drop_column("ldr_version")
