"""Backfill every tenant's settings from strategist_summaries into strategist_settings.

Settings used to be appended as JSON rows in strategist_summaries under
ingestion_type 'tool_settings' (and before that, a flat 'email_settings' blob).
They now live in strategist_settings, one row per key, updated in place.

This copies the latest stored blob for each tenant into the new table. The old
rows are left alone: they are the rollback path, and the application still reads
them for any tenant this has not reached.

Safe to run repeatedly. A tenant that already has a non-empty settings row is
skipped, so a second run does not overwrite changes made after the first.

There is no migration framework in this codebase. Tables are created by
bootstrap_tenant, which runs CREATE TABLE IF NOT EXISTS and is safe to repeat.
Pass --ensure-tables to run it across every real tenant, so strategist_settings
exists everywhere rather than appearing lazily on a tenant's first save.

    PYTHONPATH=. .venv/bin/python scripts/migrate_settings.py                   # dry run
    PYTHONPATH=. .venv/bin/python scripts/migrate_settings.py --apply --ensure-tables
    PYTHONPATH=. .venv/bin/python scripts/migrate_settings.py --apply --tenant org_abc
"""
import argparse
import json
import logging
import sys

from dotenv import load_dotenv

load_dotenv()

from app.services.infra.database import (  # noqa: E402
    bootstrap_tenant, get_db_connection, get_setting, update_setting,
)
from app.services.chat.tools_settings import (  # noqa: E402
    SETTINGS_KEY, apply_over_defaults, read_legacy_blob,
)

logging.basicConfig(level=logging.WARNING, format="%(levelname)s %(message)s")


def tenant_schemas():
    conn = get_db_connection()
    try:
        with conn.cursor() as cur:
            cur.execute("SELECT schema_name FROM information_schema.schemata "
                        "WHERE schema_name LIKE 'org\\_%' ORDER BY schema_name")
            return [r[0] for r in cur.fetchall()]
    finally:
        conn.close()


def has_summaries_table(tenant_id: str) -> bool:
    conn = get_db_connection()
    try:
        with conn.cursor() as cur:
            cur.execute(
                "SELECT EXISTS (SELECT FROM information_schema.tables "
                "WHERE table_schema = %s AND table_name = 'strategist_summaries')",
                (tenant_id,))
            return cur.fetchone()[0]
    finally:
        conn.close()


def describe(settings: dict) -> str:
    off = sorted(n for n, on in settings["features"].items() if not on)
    header = settings["email"]["header"]
    return f"off={off or 'none'} header={header[:34]!r}"


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--apply", action="store_true",
                    help="write the changes; without this it only reports")
    ap.add_argument("--tenant", help="migrate one tenant instead of all")
    ap.add_argument("--ensure-tables", action="store_true",
                    help="create strategist_settings on every real tenant, then migrate")
    args = ap.parse_args()

    schemas = [args.tenant] if args.tenant else tenant_schemas()
    print(f"{'APPLYING' if args.apply else 'DRY RUN'} — {len(schemas)} tenant schema(s)\n")

    if args.ensure_tables:
        # There is no migration framework here; the codebase creates tables via
        # bootstrap_tenant, which is idempotent. Restricted to schemas that have
        # been ingested into, so abandoned or malformed schemas are left alone
        # rather than being given a fresh set of tables.
        real = [t for t in schemas if has_summaries_table(t)]
        print(f"ensuring strategist_settings on {len(real)} real tenant(s)")
        for tenant_id in real:
            if not args.apply:
                print(f"  would    {tenant_id[:20]}  create table if missing")
                continue
            try:
                bootstrap_tenant(tenant_id)
            except Exception as ex:
                print(f"  FAIL     {tenant_id}: {ex}")
        print()

    migrated = skipped_done = skipped_empty = failed = 0

    for tenant_id in schemas:
        if not has_summaries_table(tenant_id):
            skipped_empty += 1
            continue

        try:
            existing = get_setting(tenant_id, SETTINGS_KEY)
        except Exception as ex:
            print(f"  FAIL     {tenant_id}: could not read settings table: {ex}")
            failed += 1
            continue

        if existing:
            print(f"  skip     {tenant_id[:20]}  already migrated")
            skipped_done += 1
            continue

        legacy = read_legacy_blob(tenant_id)
        if not legacy:
            skipped_empty += 1
            continue

        settings = apply_over_defaults(legacy)
        if not args.apply:
            print(f"  would    {tenant_id[:20]}  {describe(settings)}")
            migrated += 1
            continue

        try:
            # The table only exists on tenants bootstrapped since it was added.
            bootstrap_tenant(tenant_id)
            # Re-check under the lock: another process may have written meanwhile.
            update_setting(tenant_id, SETTINGS_KEY,
                           lambda current: current if current else settings)
            written = get_setting(tenant_id, SETTINGS_KEY)
            if json.dumps(written, sort_keys=True) != json.dumps(settings, sort_keys=True):
                print(f"  skip     {tenant_id[:20]}  written by someone else first")
                skipped_done += 1
                continue
            print(f"  migrated {tenant_id[:20]}  {describe(settings)}")
            migrated += 1
        except Exception as ex:
            print(f"  FAIL     {tenant_id}: {ex}")
            failed += 1

    print(f"\n{'would migrate' if not args.apply else 'migrated'}: {migrated}"
          f" | already done: {skipped_done}"
          f" | nothing to migrate: {skipped_empty}"
          f" | failed: {failed}")
    if not args.apply and migrated:
        print("\nRe-run with --apply to write these.")
    return 1 if failed else 0


if __name__ == "__main__":
    sys.exit(main())
