"""Retire the legacy fundamentals settings (A6)

Revision ID: 029
Revises: 028
Create Date: 2026-08-07 00:00:00.000000

A6 removed the FMP/Finnhub/Alpha Vantage providers, the weekly
``fundamental_collector`` job and the A5 parity report. Five SystemSetting rows
are left over. They are NOT all deleted, because the deploy runs migrations
before restarting the service: for a short window — and for the whole of any
rollback — pre-A6 code is still live, and it reads absent rows permissively
(cutover absent -> disabled; ``job_<name>_enabled`` absent -> enabled). Deleting
both would hand a rolled-back process a re-armed legacy collector writing over
the SEC/Dolt cache.

So the two rows that carry behavior become tombstones pinned to the safe value,
and only the inert ones are deleted. The tombstones are dropped in a later
release once the rollback window has closed; ``SettingsForm`` hides them
meanwhile.
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


revision: str = "029"
down_revision: Union[str, None] = "028"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


# Behavior-bearing under pre-A6 code -> pin to the safe value, keep the row.
_TOMBSTONES: dict[str, str] = {
    "fundamental_data_sec_dolt_cutover_enabled": "true",
    "job_fundamental_collector_enabled": "false",
}

# Inert either way: an absent cron falls back to a default for a job that no
# longer registers, and the parity report never wrote anything.
_OBSOLETE: tuple[str, ...] = (
    "schedule_fundamentals_cron",
    "schedule_fundamentals_parity_cron",
    "job_fundamentals_parity_report_enabled",
)

_settings = sa.table(
    "system_settings",
    sa.column("id", sa.Integer),
    sa.column("key", sa.String),
    sa.column("value", sa.Text),
    sa.column("updated_at", sa.DateTime(timezone=True)),
)


def upgrade() -> None:
    conn = op.get_bind()
    now = sa.func.now()

    for key, pinned in _TOMBSTONES.items():
        row = conn.execute(
            sa.select(_settings.c.value).where(_settings.c.key == key)
        ).fetchone()
        old_value = row[0] if row is not None else None
        print(f"a6_tombstone {key}: {old_value!r} -> {pinned!r}", flush=True)
        if row is None:
            conn.execute(
                sa.insert(_settings).values(key=key, value=pinned, updated_at=now)
            )
        elif old_value != pinned:
            conn.execute(
                sa.update(_settings)
                .where(_settings.c.key == key)
                .values(value=pinned, updated_at=now)
            )

    # Print the value before deleting — a bare DELETE cannot be undone from the
    # migration output.
    for key in _OBSOLETE:
        row = conn.execute(
            sa.select(_settings.c.value).where(_settings.c.key == key)
        ).fetchone()
        if row is None:
            print(f"a6_delete {key}: absent", flush=True)
            continue
        print(f"a6_delete {key}: {row[0]!r}", flush=True)
        conn.execute(sa.delete(_settings).where(_settings.c.key == key))


def downgrade() -> None:
    """No-op.

    The deleted rows configured jobs this revision's code no longer registers,
    and the tombstones already hold the values pre-A6 code needs. Recreating
    them would restore nothing useful; the printed values above cover recovery.
    """
