from __future__ import annotations

import argparse
import json
import os
import sys
import time

from sqlalchemy import text


def _log(message: str) -> None:
    print(f"[{time.strftime('%H:%M:%S')}] {message}", flush=True)


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Seed kitchen-cabinet taxonomy assignments from kitchen_cabinets_taxonomy.json and reconcile SKU listing paths."
    )
    parser.add_argument("--file", required=True, help="Path to kitchen_cabinets_taxonomy.json")
    parser.add_argument("--brochure-folder", required=True, help="Folder containing brochure/site JSON payloads")
    parser.add_argument("--channel-code", default=None, help="Optional collection commerce profile channel code")
    parser.add_argument("--connection-id", type=int, default=None, help="Optional collection commerce profile connection id")
    parser.add_argument("--skip-reconcile", action="store_true", help="Seed taxonomy groups but do not reassign SKU listing paths")
    parser.add_argument("--apply", action="store_true", help="Persist changes; default is dry-run")
    parser.add_argument(
        "--lock-timeout",
        default=os.getenv("IMPORT_LOCK_TIMEOUT", "15s"),
        help="Postgres lock_timeout (default 15s). Fails fast instead of hanging forever on locks.",
    )
    parser.add_argument(
        "--statement-timeout",
        default=os.getenv("IMPORT_STATEMENT_TIMEOUT", "30min"),
        help="Postgres statement_timeout (default 30min).",
    )
    args = parser.parse_args()

    from db.brochure_taxonomy import reconcile_brochure_taxonomy_assignments
    from db.kitchen_cabinets_taxonomy_import import seed_kitchen_cabinets_taxonomy
    from db.session import get_session_factory

    _log(f"pid={os.getpid()} apply={bool(args.apply)} skip_reconcile={bool(args.skip_reconcile)}")
    _log(f"file={args.file}")
    _log(f"brochure_folder={args.brochure_folder}")
    _log(f"lock_timeout={args.lock_timeout} statement_timeout={args.statement_timeout}")
    _log("If this hangs: kill -9 this pid, then check pg_stat_activity for blockers.")
    started = time.time()

    # Do NOT use get_session() here — it auto-commits on success and would persist
    # dry-runs / leave a giant open txn. Manage commit/rollback explicitly.
    session = get_session_factory()()
    try:
        # Fail fast on lock waits (Ctrl+C cannot interrupt a blocked DB socket read).
        session.execute(text(f"SET lock_timeout = '{args.lock_timeout}'"))
        session.execute(text(f"SET statement_timeout = '{args.statement_timeout}'"))
        session.execute(text("SET application_name = 'import_kitchen_cabinets_taxonomy'"))

        _log("seeding taxonomy nodes + brochure groups…")
        result = seed_kitchen_cabinets_taxonomy(
            session,
            taxonomy_path=args.file,
            brochure_folder=args.brochure_folder,
            channel_code=args.channel_code,
            connection_id=args.connection_id,
        )
        seeded = result.get("seeded_collections") or []
        skipped = result.get("skipped_collections") or []
        _log(
            f"seed done in {time.time() - started:.1f}s: "
            f"seeded_collections={len(seeded)} skipped={len(skipped)} "
            f"node_seed={result.get('node_seed')}"
        )

        if not args.skip_reconcile:
            _log("reconciling brochure → master listing paths…")
            reconcile_started = time.time()
            reconcile_result = reconcile_brochure_taxonomy_assignments(
                session,
                replace_existing=True,
                dry_run=not args.apply,
                progress=_log,
            )
            result["reconcile"] = reconcile_result
            _log(
                f"reconcile done in {time.time() - reconcile_started:.1f}s: "
                f"sku_count={reconcile_result.get('sku_count')} "
                f"path_count={reconcile_result.get('path_count')} "
                f"upserted={reconcile_result.get('upserted') or reconcile_result.get('would_upsert')}"
            )
        else:
            _log("skip-reconcile set; not rewriting listing paths")

        if args.apply:
            _log("committing transaction…")
            session.commit()
            _log("commit complete")
        else:
            _log("dry-run: rolling back session")
            session.rollback()
    except KeyboardInterrupt:
        _log("interrupted — rolling back")
        session.rollback()
        raise SystemExit(130) from None
    except Exception as exc:
        session.rollback()
        msg = str(exc)
        _log(f"FAILED: {type(exc).__name__}: {msg}")
        if "lock" in msg.lower() or "timeout" in msg.lower():
            _log(
                "Likely waiting on another DB session (recover script, worker, stale import). "
                "Run: SELECT pid, state, wait_event_type, left(query,120), now()-xact_start "
                "FROM pg_stat_activity WHERE datname = current_database() AND pid <> pg_backend_pid();"
            )
        raise
    finally:
        session.close()

    _log(f"finished in {time.time() - started:.1f}s")
    print(json.dumps(result, indent=2, default=str), flush=True)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("interrupted", file=sys.stderr, flush=True)
        raise SystemExit(130) from None
