"""
=========================================
CV6 AI Trading OS — Production-Readiness Validation Suite (Phase A, offline)
=========================================
Consolidates every in-process check into one re-runnable suite. Boots the app
in-process (no live broker, no market hours needed) and uses fake brokers to
validate the source of truth, order execution, dashboard consistency, risk
gates, market-time rules, real-indicator gating, and connection-resilience
logic.

Run:  python validate_cv6.py
Exit code 0 = all PASS, 1 = one or more FAIL.
Writes a machine-readable report to validation_report.json.
"""

import json
import sys
import time
from datetime import datetime, timedelta

# ── Quiet SQLAlchemy echo + loguru for a readable report ────────────────────
import logging
logging.getLogger("sqlalchemy").setLevel(logging.WARNING)

import main  # boots app, creates tables, runs migrations
from app.database.session import engine as _engine
_engine.echo = False   # disable SQL echo (keeps the report clean on Windows)
from loguru import logger as _loguru
_loguru.remove()       # silence app INFO/WARN chatter during the suite
from fastapi.testclient import TestClient

from app.database.session import SessionLocal
from app.state.state_models import PositionRecord, CapitalState, OrderRecord, TradingEventRecord
from app.state.trading_state_manager import trading_state_manager as TSM, TradingStateManager
from app.state.trading_events import TradingEvent, EventType
from app.execution.execution_models import ExecutionRequest, OrderSide, OrderType, ProductType
from app.execution.order_manager import OrderManager
from app.dashboard.dashboard_service import dashboard_service
from app.autonomous.autonomous_engine import autonomous_engine
from app.risk_guard.risk_guard import risk_guard, RiskVerdict

CHECKS = []


def check(section, name):
    def deco(fn):
        CHECKS.append((section, name, fn))
        return fn
    return deco


# ── Shared helpers ──────────────────────────────────────────────────────────
def reset_state(total=1_000_000.0):
    """Deterministic clean slate for the source of truth."""
    db = SessionLocal()
    try:
        db.query(PositionRecord).delete()
        db.query(CapitalState).delete()
        db.query(OrderRecord).delete()
        db.commit()
    finally:
        db.close()
    TSM.bootstrap(default_total_capital=total)
    TSM._total_capital = total
    TSM._available_capital = total
    TSM._deployed_capital = 0.0
    TSM._realized_pnl = 0.0
    TSM._positions.clear()
    TSM._orders.clear()


class FakeBroker:
    """A configurable fake broker for the execution path."""
    def __init__(self, existing_positions=None, order_book=None, entry_id="ENTRY1", sl_id="SL99"):
        self._positions = existing_positions or []
        self._order_book = order_book
        self._entry_id = entry_id
        self._sl_id = sl_id
        self.placed = []
        self.cancelled = []
    def positions(self):
        return {"data": self._positions}
    def orders(self):
        if self._order_book is not None:
            return {"data": self._order_book}
        return {"data": [{"orderid": self._entry_id, "status": "complete",
                          "filledshares": 999999, "averageprice": 100.0,
                          "tradingsymbol": "X", "transactiontype": "BUY", "quantity": 999999}]}
    def place_order(self, od):
        self.placed.append(od)
        return {"success": True, "order_id": self._sl_id if od.get("ordertype") == "SL-M" else self._entry_id}
    def cancel_order(self, oid):
        self.cancelled.append(oid)
        return {"success": True, "message": "cancelled"}


FakeBroker._connected = True   # brokers are treated as connected in these tests


# =====================================================================
# A1 — Boot integrity
# =====================================================================
@check("A1", "app boots with all routes + TSM tables + migration")
def a1_boot():
    routes = len(main.app.routes)
    assert routes >= 160, f"only {routes} routes"
    from sqlalchemy import inspect
    from app.database.session import engine
    tables = set(inspect(engine).get_table_names())
    for t in ("tsm_orders", "tsm_positions", "tsm_capital", "tsm_events",
              "trade_history", "trade_learning", "idempotency_records"):
        assert t in tables, f"missing table {t}"
    cols = {c["name"] for c in inspect(engine).get_columns("tsm_positions")}
    assert "sl_order_id" in cols, "migration did not add sl_order_id"
    return f"{routes} routes, all tables present, migration applied"


# =====================================================================
# A2 — TSM source of truth
# =====================================================================
@check("A2", "event lifecycle: open -> tick -> close capital/PnL math")
def a2_lifecycle():
    reset_state(1_000_000)
    TSM.apply_event(TradingEvent(event_type=EventType.ORDER_FILLED, symbol="RELIANCE",
        broker="angelone", side="BUY", order_id="O1", filled_quantity=10, avg_price=2800.0,
        fill_status="FILLED", style="INTRADAY", mode="REAL"))
    s = TSM.snapshot()
    assert s["capital"]["deployed"] == 28000.0 and s["position_count"] == 1
    TSM.apply_event(TradingEvent(event_type=EventType.PRICE_TICK, symbol="RELIANCE", ltp=2850.0))
    assert TSM.snapshot()["pnl"]["unrealized"] == 500.0
    TSM.apply_event(TradingEvent(event_type=EventType.POSITION_CLOSED, symbol="RELIANCE",
        avg_price=2850.0, filled_quantity=10, reason="TARGET_HIT", mode="REAL"))
    s = TSM.snapshot()
    assert s["pnl"]["realized"] == 500.0 and s["capital"]["available"] == 1_000_500.0
    assert s["capital"]["deployed"] == 0.0 and s["position_count"] == 0
    return "capital deployed/returned + realized PnL correct"


@check("A2", "one-symbol-one-position: same-side averages, no duplicate")
def a2_one_position():
    reset_state()
    for oid, p, q in [("A", 100.0, 10), ("B", 110.0, 10)]:
        TSM.apply_event(TradingEvent(event_type=EventType.ORDER_FILLED, symbol="TCS",
            broker="angelone", side="BUY", order_id=oid, filled_quantity=q, avg_price=p,
            fill_status="FILLED", mode="REAL"))
    s = TSM.snapshot()
    assert s["position_count"] == 1
    pos = TSM.get_position("TCS")
    assert pos["qty"] == 20 and abs(pos["entry_price"] - 105.0) < 1e-6
    return "second same-side fill averaged into one position (qty 20 @ 105)"


@check("A2", "restart recovery: fresh instance rebuilds from DB")
def a2_recovery():
    reset_state(750_000)
    TSM.apply_event(TradingEvent(event_type=EventType.ORDER_FILLED, symbol="INFY",
        broker="angelone", side="BUY", order_id="R1", filled_quantity=20, avg_price=1500.0,
        fill_status="FILLED", style="SWING", stop_loss=1470, sl_order_id="SLR", mode="REAL"))
    fresh = TradingStateManager()
    fresh.bootstrap()
    s = fresh.snapshot()
    assert s["position_count"] == 1 and s["positions"][0]["symbol"] == "INFY"
    assert s["capital"]["deployed"] == 30000.0 and s["capital"]["total"] == 750_000.0
    assert fresh.get_position("INFY")["sl_order_id"] == "SLR"
    return "position + capital + sl_order_id restored from DB after simulated restart"


@check("A2", "broker reconcile: restore + out-of-band close + failed-fetch safety")
def a2_reconcile():
    reset_state()
    # restore an untracked broker position
    TSM.reconcile_with_broker([{"symbol": "HDFCBANK", "exchange": "NSE", "broker": "angelone",
        "netqty": 15, "avg_price": 1600.0, "ltp": 1620.0}])
    assert TSM.has_open_position("HDFCBANK")
    # out-of-band close: broker reports flat while we hold it open
    TSM.apply_event(TradingEvent(event_type=EventType.ORDER_FILLED, symbol="WIPRO", broker="angelone",
        side="BUY", order_id="W1", filled_quantity=10, avg_price=500.0, fill_status="FILLED", mode="REAL"))
    TSM.apply_event(TradingEvent(event_type=EventType.PRICE_TICK, symbol="WIPRO", ltp=520.0))
    TSM.reconcile_with_broker([], close_missing=True)  # authoritative empty fetch
    assert not TSM.has_open_position("WIPRO")
    # failed fetch (close_missing False) must NOT wipe positions
    TSM.apply_event(TradingEvent(event_type=EventType.ORDER_FILLED, symbol="ITC", broker="angelone",
        side="BUY", order_id="I1", filled_quantity=10, avg_price=400.0, fill_status="FILLED", mode="REAL"))
    TSM.reconcile_with_broker([], close_missing=False)
    assert TSM.has_open_position("ITC")
    return "restore, out-of-band close, and failed-fetch safety all correct"


@check("A2", "idempotency is DB-backed and survives restart")
def a2_idempotency():
    import app.execution.idempotency as idem
    key = idem.explicit_key("val-suite-key")
    db = SessionLocal()
    try:
        from app.execution.idempotency_model import IdempotencyRecord
        db.query(IdempotencyRecord).filter(IdempotencyRecord.key == key).delete()
        db.commit()
    finally:
        db.close()
    assert idem.get_cached(key) is None
    idem.store(key, {"success": True, "order_id": "X1"})
    got = idem.get_cached(key)
    assert got is not None and got["order_id"] == "X1"
    return "explicit key persisted + retrievable across sessions"


# =====================================================================
# A3 — Order execution
# =====================================================================
@check("A3", "idempotency prevents duplicate broker order")
def a3_dedup():
    reset_state()
    saved = risk_guard.check_entry
    risk_guard.check_entry = lambda **kw: RiskVerdict.ok()
    try:
        om = OrderManager(); fb = FakeBroker(entry_id="D1")
        om.broker_manager.get_broker = lambda name=None: fb
        req = ExecutionRequest(broker="angelone", symbol="DEDUP", exchange="NSE", side=OrderSide.BUY,
            quantity=5, order_type=OrderType.MARKET, product=ProductType.INTRADAY, idempotency_key="dd1")
        fb._order_book = [{"orderid": "D1", "status": "complete", "filledshares": 5, "averageprice": 100.0,
                           "tradingsymbol": "DEDUP", "transactiontype": "BUY", "quantity": 5}]
        r1 = om.place_order(req)
        entry_calls = [o for o in fb.placed if o.get("ordertype") == "MARKET"]
        r2 = om.place_order(req)
        entry_calls2 = [o for o in fb.placed if o.get("ordertype") == "MARKET"]
        assert len(entry_calls) == 1 and len(entry_calls2) == 1, "duplicate market order placed"
        assert r1.order_id == r2.order_id
    finally:
        risk_guard.check_entry = saved
    return "same idempotency key -> broker hit once"


@check("A3", "position guard blocks pyramiding, allows reduce")
def a3_guard():
    reset_state()
    saved = risk_guard.check_entry
    risk_guard.check_entry = lambda **kw: RiskVerdict.ok()
    try:
        om = OrderManager()
        fb = FakeBroker(existing_positions=[{"tradingsymbol": "HELD", "exchange": "NSE", "netqty": 10, "averageprice": 100}])
        om.broker_manager.get_broker = lambda name=None: fb
        buy = ExecutionRequest(broker="angelone", symbol="HELD", exchange="NSE", side=OrderSide.BUY,
            quantity=5, order_type=OrderType.MARKET, product=ProductType.INTRADAY)
        r = om.place_order(buy)
        assert r.success is False and r.status == "BLOCKED"
        sell = ExecutionRequest(broker="angelone", symbol="HELD", exchange="NSE", side=OrderSide.SELL,
            quantity=5, order_type=OrderType.MARKET, product=ProductType.INTRADAY)
        fb._order_book = [{"orderid": "ENTRY1", "status": "complete", "filledshares": 5, "averageprice": 100.0,
                           "tradingsymbol": "HELD", "transactiontype": "SELL", "quantity": 5}]
        r2 = om.place_order(sell)
        assert r2.status != "BLOCKED"
    finally:
        risk_guard.check_entry = saved
    return "second same-side BUY blocked; opposite SELL allowed through"


@check("A3", "partial fill reported with true filled qty")
def a3_partial():
    from app.execution import fill_tracker
    class PB:
        def orders(self):
            return {"data": [{"orderid": "PF1", "status": "open", "filledshares": 3,
                              "tradingsymbol": "PART", "averageprice": 101.5}]}
    res = fill_tracker.poll_until_final(PB(), order_id="PF1", symbol="PART", side="BUY",
        quantity=10, max_wait_secs=1.5, interval_secs=0.4)
    assert res.status == "PARTIALLY_FILLED" and res.filled_qty == 3
    return "partial fill -> PARTIALLY_FILLED with filled_qty=3 (not requested 10)"


@check("A3", "ambiguous broker-timeout reconciled to real fill")
def a3_ambiguous():
    reset_state()
    saved = risk_guard.check_entry
    risk_guard.check_entry = lambda **kw: RiskVerdict.ok()
    try:
        class TB:
            def positions(self): return {"data": []}
            def place_order(self, od): return {"success": False, "message": "Read timed out. (read timeout=15)"}
            def orders(self):
                return {"data": [{"orderid": "GHOST1", "status": "complete", "filledshares": 8,
                    "averageprice": 305.0, "tradingsymbol": "TMO", "transactiontype": "BUY", "quantity": 8}]}
        om = OrderManager(); om.broker_manager.get_broker = lambda name=None: TB()
        req = ExecutionRequest(broker="angelone", symbol="TMO", exchange="NSE", side=OrderSide.BUY,
            quantity=8, order_type=OrderType.MARKET, product=ProductType.INTRADAY, idempotency_key="amb1")
        r = om.place_order(req)
        assert r.fill_status == "FILLED" and r.order_id == "GHOST1"
    finally:
        risk_guard.check_entry = saved
    return "timed-out order that actually filled is reconciled (not lost)"


@check("A3", "broker-native SL-M placed opposite-side at trigger after fill")
def a3_sl():
    reset_state()
    saved = risk_guard.check_entry
    risk_guard.check_entry = lambda **kw: RiskVerdict.ok()
    try:
        om = OrderManager()
        fb = FakeBroker(entry_id="E1", sl_id="SLX")
        fb._order_book = [{"orderid": "E1", "status": "complete", "filledshares": 10, "averageprice": 2800.0,
                           "tradingsymbol": "REL", "transactiontype": "BUY", "quantity": 10}]
        om.broker_manager.get_broker = lambda name=None: fb
        req = ExecutionRequest(broker="angelone", symbol="REL", exchange="NSE", side=OrderSide.BUY,
            quantity=10, order_type=OrderType.MARKET, product=ProductType.INTRADAY,
            stop_loss=2750.0, target_price=2900.0, idempotency_key="sl1")
        r = om.place_order(req)
        assert r.sl_protected and r.sl_order_id == "SLX"
        sl = [o for o in fb.placed if o.get("ordertype") == "SL-M"]
        assert len(sl) == 1
        assert sl[0]["variety"] == "STOPLOSS" and sl[0]["transactiontype"] == "SELL"
        assert float(sl[0]["triggerprice"]) == 2750.0 and sl[0]["quantity"] == 10
        assert TSM.get_position("REL")["sl_order_id"] == "SLX"
    finally:
        risk_guard.check_entry = saved
    return "SL-M placed (SELL, trigger 2750, qty 10) + persisted to TSM"


@check("A3", "monitor close action carries sl_order_id + real qty (OCO input)")
def a3_oco_action():
    from app.autonomous.capital_pool import CapitalPool, AutonomousPosition
    from app.autonomous.position_monitor import position_monitor
    pool = CapitalPool(style="INTRADAY", allocated_capital=100000, broker="angelone")
    pos = AutonomousPosition(symbol="TCS", style="INTRADAY", side="BUY", qty=8,
        entry_price=4000, stop_loss=3950, target_price=4100, broker="angelone", exchange="NSE")
    pos.sl_order_id = "SLABC"
    pool.add_position(pos)
    actions = position_monitor.monitor_pool(pool, {"TCS": 4105.0}, trading_end_str="23:59")
    a = actions[0]
    assert a["reason"] == "TARGET_HIT" and a["sl_order_id"] == "SLABC" and a["qty"] == 8
    assert a["side"] == "BUY" and a["broker"] == "angelone"
    return "close action carries sl_order_id, real qty, side, broker for OCO cancel"


# =====================================================================
# A4 — Dashboard / portfolio consistency
# =====================================================================
@check("A4", "REAL activity -> dashboard reads TSM (not paper)")
def a4_real():
    autonomous_engine.config.mode = "REAL"
    reset_state(500_000)
    TSM.apply_event(TradingEvent(event_type=EventType.ORDER_FILLED, symbol="LT", broker="angelone",
        side="BUY", order_id="L1", filled_quantity=4, avg_price=3500.0, fill_status="FILLED",
        style="SWING", mode="REAL"))
    TSM.apply_event(TradingEvent(event_type=EventType.PRICE_TICK, symbol="LT", ltp=3550.0))
    db = SessionLocal()
    try:
        snap = dashboard_service.snapshot(db)
    finally:
        db.close()
    assert dashboard_service._tsm_active() is True
    assert snap.funds.broker == "live" and snap.funds.available_cash == 486000.0
    assert any(p.symbol == "LT" and p.pnl == 200.0 for p in snap.portfolio)
    # consistency: dashboard PnL == TSM snapshot PnL
    t = TSM.snapshot()["pnl"]
    assert abs(snap.pnl.unrealized_pnl - t["unrealized"]) < 1e-6
    assert abs(snap.pnl.realized_pnl - t["realized"]) < 1e-6
    return "dashboard funds/positions/pnl sourced from TSM and consistent with /state"


@check("A4", "mode-aware: PAPER + no real activity -> paper fallback")
def a4_paper():
    reset_state()
    TSM._positions.clear(); TSM._deployed_capital = 0.0
    autonomous_engine.config.mode = "PAPER"
    try:
        assert dashboard_service._tsm_active() is False
    finally:
        autonomous_engine.config.mode = "REAL"
    return "paper mode with no real activity -> dashboard uses paper engine"


# =====================================================================
# A5 — Risk engine
# =====================================================================
@check("A5", "kill switch blocks manual order path")
def a5_kill():
    reset_state()
    risk_guard.activate_kill_switch("validation")
    try:
        om = OrderManager(); om.broker_manager.get_broker = lambda name=None: FakeBroker()
        req = ExecutionRequest(broker="angelone", symbol="ZEEL", exchange="NSE", side=OrderSide.BUY,
            quantity=1, order_type=OrderType.MARKET, product=ProductType.INTRADAY, idempotency_key="kill1")
        r = om.place_order(req)
        assert r.success is False and r.status == "BLOCKED" and "Risk blocked" in r.message
    finally:
        risk_guard.deactivate_kill_switch()
    return "manual order blocked while kill switch active"


@check("A5", "kill switch + market-hours gate reject entries")
def a5_gates():
    v = risk_guard.check_entry(symbol="X", sector="Other", ltp=100.0, change_pct=0.0, vix=0.0,
        news_sentiment="NEUTRAL", open_positions=[], capital=100000, deployed=0, mode="REAL", exchange="NSE")
    # On a weekend the market-hours layer should block; kill-switch layer proven above.
    assert v.allowed is False
    return f"entry correctly blocked by risk layer [{v.layer}]"


# =====================================================================
# A6 — Market-time rules
# =====================================================================
@check("A6", "EOD square-off timing (NSE 15:10 / MCX 23:00) via simulated time")
def a6_eod():
    from app.autonomous.position_monitor import position_monitor
    d = datetime(2026, 7, 6)  # a Monday
    # 15:07 is within 5 min of 15:10 -> EOD true
    assert position_monitor._is_eod(d.replace(hour=15, minute=7), "15:10") is True
    # 12:00 is far from close -> False
    assert position_monitor._is_eod(d.replace(hour=12, minute=0), "15:10") is False
    # MCX 22:58 within 5 min of 23:00 -> True
    assert position_monitor._is_eod(d.replace(hour=22, minute=58), "23:00") is True
    return "EOD window correct for NSE 15:10 and MCX 23:00"


# =====================================================================
# A7 — Real indicators + fabrication gate
# =====================================================================
@check("A7", "real indicator math (Wilder RSI/ATR, EMA, Supertrend)")
def a7_math():
    from app.market import technicals as T
    closes = [100,101,102,101,103,104,103,105,106,105,107,108,107,109,110,109,111,112,111,113]
    highs = [c+1 for c in closes]; lows = [c-1 for c in closes]
    rsi = T._wilder_rsi(closes); atr = T._wilder_atr(highs, lows, closes)
    assert 60 < rsi <= 100 and atr > 0
    assert T._supertrend(highs, lows, closes) in ("UP", "DOWN", "NEUTRAL")
    return f"RSI={rsi} ATR={round(atr,2)} on uptrend series"


@check("A7", "no real data -> UNAVAILABLE (never fabricated); real candles -> real")
def a7_gate():
    from app.market.technicals import compute_technicals
    t = compute_technicals("UNSEEN_SYM_1", "NSE", broker=None)
    assert t.data_quality == "unavailable" and t.rsi == 0.0
    class FA:
        def _get_symbol_token(self, s, e): return (s, "2885")
        def historical_data(self, params):
            rows = []; base = 2800; now = datetime.now()
            for i in range(60, 0, -1):
                p = base + (60 - i) * 2
                ts = (now - timedelta(minutes=5*i)).strftime("%Y-%m-%d %H:%M:%S")
                rows.append([ts, p, p+3, p-2, p+1, 100000+i*10])
            return {"status": True, "data": rows}
    t2 = compute_technicals("UNSEEN_SYM_2", "NSE", broker=FA())
    assert t2.data_quality == "real" and t2.candles_used >= 30 and t2.rsi > 50
    return "unavailable never fabricates; real candles -> data_quality=real"


@check("A7", "AI prompt states data quality honestly")
def a7_prompt():
    from app.autonomous.ai_context_builder import AIContextPackage
    sim = AIContextPackage(symbol="Z", data_quality="simulated").to_prompt()
    assert "SIMULATED" in sim and "pre-fetched from real sources" not in sim
    return "prompt flags SIMULATED; false 'real sources' claim removed"


# =====================================================================
# A8 — Resilience logic
# =====================================================================
@check("A8", "Angel One expiry detection + sdk_call relogin+retry")
def a8_angel():
    from app.broker.angel_one import AngelOneBroker
    b = AngelOneBroker()
    assert b._is_session_expired({"errorcode": "AG8001", "message": "Invalid Token"})
    assert b._is_session_expired({}, status_code=401)
    assert not b._is_session_expired({"status": True, "data": {}})
    calls = {"n": 0, "relogin": 0}
    class FS:
        def orderBook(self):
            calls["n"] += 1
            return {"status": False, "errorcode": "AG8002"} if calls["n"] == 1 else {"status": True, "data": []}
    b.smart_api = FS(); b.connected = True
    b._relogin = lambda: (calls.__setitem__("relogin", calls["relogin"] + 1) or True)
    res = b._sdk_call("orderBook")
    assert res["status"] is True and calls["n"] == 2 and calls["relogin"] == 1
    return "expiry detected; one relogin + retry then success"


@check("A8", "live-feed reconnect refreshes tokens + respawns; disconnect halts it")
def a8_feed():
    from app.market.live_feed import LiveFeedManager
    lf = LiveFeedManager()
    class FB2:
        def __init__(self): self.jwt_token = "JWT_NEW"; self.feed_token = "FEED_NEW"; self.client_id = "C1"; self.v = 0
        def is_connected(self): return True
        def validate_session(self): self.v += 1; return True
    lf._broker = FB2(); lf._should_run = True; lf._connected = False
    spawned = {"n": 0}
    def fake_spawn():
        spawned["n"] += 1; lf._connected = True
    lf._spawn_ws = fake_spawn
    ok = lf._reconnect()
    assert ok and spawned["n"] == 1 and lf._auth_token == "JWT_NEW" and lf._feed_token == "FEED_NEW"
    lf.disconnect()
    assert lf._should_run is False and lf._stop_supervisor is True
    return "reconnect refreshes tokens + respawns; disconnect stops supervisor"


# =====================================================================
# A9 — Backtest realism (static)
# =====================================================================
@check("A9", "backtest core has no random P&L fabrication")
def a9_backtest():
    import re
    import pathlib
    root = pathlib.Path("app/backtest")
    offenders = []
    for f in ["backtest_engine.py", "backtest_broker.py", "backtest_reporter.py"]:
        p = root / f
        if not p.exists():
            continue
        src = p.read_text(encoding="utf-8", errors="ignore")
        for pat in ("random.", "np.random", "randint(", "uniform(", ".choice("):
            if pat in src:
                offenders.append(f"{f}:{pat}")
    assert not offenders, f"random fabrication found: {offenders}"
    return "no random/np.random/uniform/choice in backtest core"


# ── Runner ──────────────────────────────────────────────────────────────────
def run():
    # Clean slate so the suite is repeatable: idempotency records persist by
    # design (A2 validates that), so stale keys from a prior run would return
    # cached responses. Clear them before this run.
    db = SessionLocal()
    try:
        from app.execution.idempotency_model import IdempotencyRecord
        db.query(IdempotencyRecord).delete()
        db.commit()
    finally:
        db.close()

    print("=" * 72)
    print("CV6 PRODUCTION-READINESS VALIDATION — Phase A (offline)")
    print(f"{datetime.now().isoformat(timespec='seconds')}")
    print("=" * 72)
    results = []
    passed = failed = 0
    for section, name, fn in CHECKS:
        try:
            detail = fn()
            results.append({"section": section, "name": name, "status": "PASS", "detail": detail})
            passed += 1
            print(f"  [PASS] {section}  {name}")
            print(f"         {detail}")
        except Exception as e:
            results.append({"section": section, "name": name, "status": "FAIL", "detail": repr(e)})
            failed += 1
            print(f"  [FAIL] {section}  {name}")
            print(f"         {e!r}")
    print("=" * 72)
    print(f"RESULT: {passed} PASS / {failed} FAIL / {len(CHECKS)} total")
    print("=" * 72)
    report = {"ts": datetime.now().isoformat(), "passed": passed, "failed": failed,
              "total": len(CHECKS), "checks": results}
    with open("validation_report.json", "w", encoding="utf-8") as f:
        json.dump(report, f, indent=2)
    return 0 if failed == 0 else 1


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