"""Migration utility: convert Python literal config values in the DB into canonical JSON.

Usage:
  python scripts/migrate_config_literals.py [--apply] [--backup=path] [--quiet]

By default the script runs in dry-run mode and prints candidate rows it would change.
Use --apply to persist changes. --backup writes a JSON file listing changed rows before applying.
"""

from __future__ import annotations

import argparse
import sqlite3
import json
import ast
from pathlib import Path
from typing import Any, Dict, List, Tuple

DB = Path("medios.db")


def _is_json_like(s: str) -> bool:
    if not isinstance(s, str):
        return False
    s = s.strip()
    if not s:
        return False
    return s[0] in '{["' or s.lower() in ("true", "false", "null") or s[0].isdigit() or s[0] == "'"


def find_candidates(conn: sqlite3.Connection) -> List[Tuple[int, str, str, str, str, str]]:
    cur = conn.cursor()
    cur.execute("SELECT rowid, category, subtype, item_name, key, value FROM config")
    rows = []
    for rowid, cat, sub, name, key, val in cur.fetchall():
        if val is None:
            continue
        s = str(val)
        if _is_json_like(s):
            try:
                json.loads(s)
            except Exception:
                # Try ast.literal_eval
                try:
                    parsed = ast.literal_eval(s)
                    # Only consider basic JSON-serializable types
                    json.dumps(parsed)
                    rows.append((rowid, cat, sub, name, key, s))
                except Exception:
                    continue
    return rows


def apply_migration(conn: sqlite3.Connection, candidates: List[Tuple[int, str, str, str, str, str]]) -> List[Tuple[int, str, str, str, str, str]]:
    cur = conn.cursor()
    changed = []
    for row in candidates:
        rowid, cat, sub, name, key, val = row
        try:
            parsed = ast.literal_eval(val)
            new_val = json.dumps(parsed, ensure_ascii=False)
            cur.execute("UPDATE config SET value = ? WHERE rowid = ?", (new_val, rowid))
            changed.append((rowid, cat, sub, name, key, new_val))
        except Exception:
            continue
    conn.commit()
    return changed


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--apply", action="store_true", help="Persist changes to DB")
    parser.add_argument("--backup", type=str, default=None, help="Path to write backup JSON of changed rows")
    parser.add_argument("--quiet", action="store_true", help="Minimize output")
    args = parser.parse_args()

    conn = sqlite3.connect(str(DB))

    candidates = find_candidates(conn)

    if not args.quiet:
        print(f"Found {len(candidates)} candidate rows for migration")
        for r in candidates[:50]:
            rowid, cat, sub, name, key, val = r
            print(f"row {rowid}: {cat}.{sub}.{name} {key} -> {val[:200]!r}")

    if not candidates:
        return 0

    if args.backup:
        out_path = Path(args.backup)
        data = [dict(rowid=r[0], category=r[1], subtype=r[2], item_name=r[3], key=r[4], value=r[5]) for r in candidates]
        out_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
        if not args.quiet:
            print(f"Wrote backup to {out_path}")

    if args.apply:
        changed = apply_migration(conn, candidates)
        if not args.quiet:
            print(f"Applied migration to {len(changed)} rows")
        return 0

    if not args.quiet:
        print("Dry-run; re-run with --apply to persist changes")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
