"""
Quantum-simulator bake-off for PHOENIX Sicily placement.

Runs, on the simulator / CPU only:
  - exhaustive enumeration of EVERY feasible placement (true global optimum + landscape)
  - dimod ExactSolver ground state of the pure-site QUBO
  - real QAOA on the Qiskit statevector simulator at depth p = 1, 2, 3
  - PennyLane QAOA (second gate-model path)
  - classical references (CP-SAT, greedy) for the same instance

Every backend is scored by the ONE true objective (risk-weighted coverage), so the
surrogate's approximation error and QAOA's gap are both visible and honest.

Usage:
    python run_quantum_sim.py                 # default small (~12-14 qubit) instance
    python run_quantum_sim.py --candidates 12 --budget 4 --grid 12
"""
from __future__ import annotations
import sys, os, json, argparse
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from geo_optimize import solve_cpsat, solve_greedy
from phoenix_sicily import build_phoenix_sicily
from quantum_sim import (build_site_qubo, exhaustive_true, exact_qubo,
                         qaoa_aer, qaoa_pennylane)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--candidates", type=int, default=14)
    ap.add_argument("--budget", type=int, default=5)
    ap.add_argument("--grid", type=int, default=12)
    ap.add_argument("--seed", type=int, default=7)
    args = ap.parse_args()

    inst = build_phoenix_sicily(demand_grid=args.grid, n_candidates=args.candidates,
                                budget_p=args.budget, seed=args.seed)
    sq = build_site_qubo(inst)
    nq = len(sq.lin)
    print(f"\n=== PHOENIX Sicily -- gate-model QUANTUM SIMULATOR bake-off ===")
    print(f"candidates={inst.C} (eligible/qubits={nq})  demand={inst.D}  budget_p={inst.budget_p}")
    print(f"pure-site QUBO: {nq} qubits  ->  statevector = 2^{nq} = {2**nq:,} amplitudes\n")

    diags = {}
    results = []

    ex_res, landscape = exhaustive_true(inst)
    results.append(ex_res)
    opt = ex_res.score
    print("EXHAUSTIVE (every feasible option, true objective):")
    for k, v in landscape.items():
        print(f"   {k:<34} {v}")
    print()

    results.append(solve_cpsat(inst))
    results.append(solve_greedy(inst))
    results.append(exact_qubo(inst))
    for p in (1, 2, 3):
        r, d = qaoa_aer(inst, p=p, restarts=3, maxiter=90, seed=args.seed)
        results.append(r); diags[r.backend] = d
    rp, dp = qaoa_pennylane(inst, p=2)
    results.append(rp); diags[rp.backend] = dp

    print(f"{'backend':<24} {'true_score':>10} {'cover%':>7} {'gap_opt':>8} {'ms':>9}")
    print("-" * 62)
    for r in sorted(results, key=lambda r: (-r.score, r.latency_ms)):
        frac = r.score / max(inst.total_risk(), 1e-9) * 100
        gap = opt - r.score
        if not r.selected and r.note.lower().startswith("skip"):
            print(f"{r.backend:<24} {'--':>10} {'--':>7} {'--':>8} {'--':>9}   ({r.note})")
        else:
            print(f"{r.backend:<24} {r.score:>10.3f} {frac:>6.1f}% {gap:>8.3f} {r.latency_ms:>9.1f}")

    print("\nQAOA diagnostics:")
    for name, d in diags.items():
        if d:
            print(f"   {name}: {d}")

    matched = [r.backend for r in results if abs(r.score - opt) < 1e-6 and r.backend != "exhaustive_true"]
    qaoa_best = max((r.score for r in results if r.backend.startswith("qaoa")), default=0.0)
    verdict = (f"Global optimum (exhaustive) = {opt:.3f}. "
               f"Backends reaching it: {', '.join(matched) if matched else 'none'}. "
               f"Best QAOA true-score = {qaoa_best:.3f} (gap {opt - qaoa_best:.3f}). ")
    verdict += ("QAOA matches the optimum after decode+repair but with no speed or "
                "quality edge over CP-SAT/exhaustive at this scale; raw QAOA sampling "
                "concentrates only weakly on the optimum -- consistent with the literature: "
                "no quantum advantage here.") if qaoa_best >= opt - 1e-6 else \
               ("QAOA did not reach the optimum even after repair — quantum-inspired/"
                "classical solvers dominate at this scale.")
    print("\nVERDICT:", verdict)

    record = {
        "instance": inst.name, "qubits": nq, "global_optimum": opt,
        "landscape": landscape,
        "backends": {r.backend: {"score": round(r.score, 4),
                                 "gap_vs_optimum": round(opt - r.score, 4),
                                 "latency_ms": round(r.latency_ms, 2),
                                 "selected": r.selected, "note": r.note}
                     for r in results},
        "qaoa_diagnostics": diags,
        "verdict": verdict,
        "product_boundary": "PHOENIX-only instance; quantum is benchmark, not dependency",
    }
    os.makedirs("results", exist_ok=True)
    out = os.path.join("results", f"{inst.name}_quantum_sim.json")
    with open(out, "w", encoding="utf-8") as f:
        json.dump(record, f, indent=2)
    print(f"\nstructured record -> {out}")


if __name__ == "__main__":
    main()
