"""
CV6 AI Trading OS — Full Integration Test
Runs 100 paper trades, queries every API endpoint,
records all backend values for frontend comparison.
Output: INTEGRATION_TEST_RESULTS.json + INTEGRATION_TEST_RESULTS.txt
"""

import requests
import json
import time
import datetime
import sys

BASE = "http://127.0.0.1:8000"
OUT_JSON = r"D:\CV6_AI_Trading_OS\INTEGRATION_TEST_RESULTS.json"
OUT_TXT  = r"D:\CV6_AI_Trading_OS\INTEGRATION_TEST_RESULTS.txt"

results = {}
log_lines = []

def log(msg):
    ts = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3]
    line = f"[{ts}] {msg}"
    print(line)
    log_lines.append(line)

def save():
    with open(OUT_JSON, "w", encoding="utf-8") as f:
        json.dump(results, f, indent=2, default=str)
    with open(OUT_TXT, "w", encoding="utf-8") as f:
        f.write("\n".join(log_lines))
    log(f"Saved: {OUT_JSON}")
    log(f"Saved: {OUT_TXT}")

def get(path, headers=None, label=None):
    try:
        r = requests.get(f"{BASE}{path}", headers=headers, timeout=10)
        data = r.json()
        log(f"  GET {path} → {r.status_code}")
        if label:
            results[label] = {"status": r.status_code, "data": data}
        return r.status_code, data
    except Exception as e:
        log(f"  GET {path} → ERROR: {e}")
        if label:
            results[label] = {"status": "ERROR", "data": str(e)}
        return None, None

def post(path, payload, headers=None, label=None):
    try:
        r = requests.post(f"{BASE}{path}", json=payload, headers=headers, timeout=10)
        data = r.json()
        if label:
            results[label] = {"status": r.status_code, "data": data}
        return r.status_code, data
    except Exception as e:
        log(f"  POST {path} → ERROR: {e}")
        if label:
            results[label] = {"status": "ERROR", "data": str(e)}
        return None, None

log("=" * 70)
log("CV6 FINAL INTEGRATION TEST — ACTUAL RUNTIME DATA")
log(f"Date: {datetime.date.today()}  Time: {datetime.datetime.now().strftime('%H:%M:%S')}")
log("=" * 70)

# ── 1. Backend health ─────────────────────────────────────────────────────────
log("\n[1] BACKEND HEALTH")
sc, data = get("/", label="root")
log(f"  Response: {data}")
if sc != 200:
    log("FATAL: Backend not running")
    save(); sys.exit(1)

# ── 2. Register + login ───────────────────────────────────────────────────────
log("\n[2] REGISTER + LOGIN")
reg = {"name": "Sasi Kumar", "email": "psasi144@gmail.com", "password": "CV6Tr@d3r2026!"}
sc, data = post("/users/register", reg, label="register")
log(f"  Register → {sc}: {data}")

login_payload = {"email": "psasi144@gmail.com", "password": "CV6Tr@d3r2026!"}
sc, data = post("/users/login", login_payload, label="login")
log(f"  Login → {sc}: {str(data)[:200]}")

token = ""
if data:
    token = (data.get("access_token") or data.get("token") or
             data.get("data", {}).get("token", ""))
    if not token:
        for k, v in data.items():
            if isinstance(v, str) and len(v) > 50:
                token = v; break

if not token:
    log("FATAL: No JWT token")
    save(); sys.exit(1)

log(f"  JWT: {token[:60]}...")
H = {"Authorization": f"Bearer {token}"}
results["jwt_token"] = token[:60] + "..."

# ── 3. Auth guard verification ────────────────────────────────────────────────
log("\n[3] AUTH GUARD VERIFICATION")
sc_no_auth, _ = get("/paper/account", label="paper_account_no_auth")
log(f"  /paper/account (no auth) → {sc_no_auth}  (expect 403)")
results["auth_guard_no_token"] = {"endpoint": "/paper/account", "status": sc_no_auth, "expected": 403, "pass": sc_no_auth == 403}

sc_no_auth2, _ = get("/dashboard/", label="dashboard_no_auth")
log(f"  /dashboard/ (no auth) → {sc_no_auth2}  (expect 403)")
results["auth_guard_dashboard"] = {"status": sc_no_auth2, "expected": 403, "pass": sc_no_auth2 == 403}

# ── 4. Initial paper account ─────────────────────────────────────────────────
log("\n[4] INITIAL PAPER ACCOUNT")
sc, acc0 = get("/paper/account", H, label="paper_account_initial")
log(f"  total_capital={acc0.get('total_capital')}  available={acc0.get('available_capital')}  pnl={acc0.get('total_pnl')}")

# ── 5. 100 Paper trades ───────────────────────────────────────────────────────
log("\n[5] EXECUTING 100 PAPER TRADES")

# Reset paper engine with fresh capital by starting with a BUY that sets capital
# Build 50 buy→sell pairs across 8 symbols
SYMBOLS = ["NIFTY","BANKNIFTY","RELIANCE","TCS","INFY","HDFCBANK","ICICIBANK","SBIN"]
PRICES  = {
    "NIFTY":    24350.0,
    "BANKNIFTY":52100.0,
    "RELIANCE":  2940.0,
    "TCS":       3820.0,
    "INFY":      1540.0,
    "HDFCBANK":  1680.0,
    "ICICIBANK":  960.0,
    "SBIN":       820.0,
}
QTYS = {
    "NIFTY":1,"BANKNIFTY":1,"RELIANCE":2,"TCS":2,
    "INFY":3,"HDFCBANK":3,"ICICIBANK":5,"SBIN":5,
}

trade_results = []
ok = fail = 0
trade_num = 0
# Use capital=500000 on first trade to give enough room
first_trade = True

# Generate 50 BUY+SELL pairs = 100 trade requests
for round_num in range(50):
    sym = SYMBOLS[round_num % len(SYMBOLS)]
    base_price = PRICES[sym]
    qty = QTYS[sym]
    entry = round(base_price * (1 + (round_num % 5) * 0.001), 2)
    exit_price = round(entry * 1.003, 2)  # +0.3% gain

    # BUY
    trade_num += 1
    buy_payload = {
        "symbol": sym,
        "action": "BUY",
        "quantity": qty,
        "entry_price": entry,
    }
    if first_trade:
        buy_payload["capital"] = 5000000  # 50 lakh — enough for all trades
        first_trade = False

    sc, resp = post("/paper/trade", buy_payload, H)
    success = sc == 200 and resp and resp.get("success")
    msg = resp.get("message", "") if resp else "no response"
    status_icon = "✅" if success else "❌"
    log(f"  Trade {trade_num:03d}: {status_icon} BUY  {qty}x {sym:10s} @ {entry:8.2f} → {msg[:60]}")
    trade_results.append({"num": trade_num, "action": "BUY", "symbol": sym,
                          "qty": qty, "price": entry, "success": success, "msg": msg})
    if success: ok += 1
    else: fail += 1

    # SELL
    trade_num += 1
    sell_payload = {
        "symbol": sym,
        "action": "SELL",
        "quantity": qty,
        "entry_price": exit_price,
    }
    sc, resp = post("/paper/trade", sell_payload, H)
    success = sc == 200 and resp and resp.get("success")
    msg = resp.get("message", "") if resp else "no response"
    status_icon = "✅" if success else "❌"
    log(f"  Trade {trade_num:03d}: {status_icon} SELL {qty}x {sym:10s} @ {exit_price:8.2f} → {msg[:60]}")
    trade_results.append({"num": trade_num, "action": "SELL", "symbol": sym,
                          "qty": qty, "price": exit_price, "success": success, "msg": msg})
    if success: ok += 1
    else: fail += 1

    time.sleep(0.1)

results["trades"] = {"total": trade_num, "success": ok, "fail": fail, "log": trade_results}
log(f"\n  TRADES COMPLETE: {ok} success / {fail} fail / {trade_num} total")

# ── 6. Post-trade account snapshot ───────────────────────────────────────────
log("\n[6] POST-TRADE ACCOUNT SNAPSHOT")
sc, acc = get("/paper/account", H, label="paper_account_final")
if acc:
    log(f"  total_capital:     {acc.get('total_capital')}")
    log(f"  available_capital: {acc.get('available_capital')}")
    log(f"  invested_amount:   {acc.get('invested_amount')}")
    log(f"  realized_pnl:      {acc.get('realized_pnl')}")
    log(f"  unrealized_pnl:    {acc.get('unrealized_pnl')}")
    log(f"  total_pnl:         {acc.get('total_pnl')}")
    log(f"  trade_count:       {acc.get('trade_count')}")
    log(f"  win_count:         {acc.get('win_count')}")
    log(f"  loss_count:        {acc.get('loss_count')}")
    log(f"  win_rate:          {acc.get('win_rate')}")
    log(f"  open_positions:    {len(acc.get('open_positions', []))} open")

# ── 7. Trade history ──────────────────────────────────────────────────────────
log("\n[7] PAPER TRADE HISTORY")
sc, hist = get("/paper/history", H, label="paper_history")
if isinstance(hist, dict):
    trades_list = hist.get("trades", [])
elif isinstance(hist, list):
    trades_list = hist
else:
    trades_list = []
log(f"  Total history records: {len(trades_list)}")
for t in trades_list[:5]:
    log(f"  {t}")

# ── 8. Dashboard ─────────────────────────────────────────────────────────────
log("\n[8] DASHBOARD API")
sc, dash = get("/dashboard/", H, label="dashboard")
if dash:
    log(f"  success:      {dash.get('success')}")
    log(f"  generated_at: {dash.get('generated_at')}")
    pnl = dash.get("pnl", {})
    log(f"  dashboard.pnl.realized_pnl: {pnl.get('realized_pnl')}")
    log(f"  dashboard.pnl.total_pnl:    {pnl.get('total_pnl')}")
    log(f"  dashboard.pnl.win_rate:     {pnl.get('win_rate')}")
    funds = dash.get("funds", {})
    log(f"  dashboard.funds: {funds}")

# KEY MISMATCH CHECK
log("\n  ⚠️  MISMATCH CHECK:")
paper_pnl = acc.get("total_pnl") if acc else None
dash_pnl  = pnl.get("total_pnl") if dash else None
log(f"  Paper Account total_pnl:  {paper_pnl}")
log(f"  Dashboard total_pnl:      {dash_pnl}")
if paper_pnl != dash_pnl:
    log(f"  ❌ MISMATCH: paper={paper_pnl} vs dashboard={dash_pnl}")
    results["mismatch_pnl"] = {
        "issue": "Dashboard pnl does not read from paper engine",
        "paper_value": paper_pnl,
        "dashboard_value": dash_pnl,
        "root_cause": "dashboard_service.py reads from broker API (Angel One), not PaperEngine",
        "fix": "dashboard_router.py /dashboard/ should include paper engine snapshot when broker not connected"
    }
else:
    log(f"  ✅ PnL values match")

# ── 9. All dashboard sub-endpoints ───────────────────────────────────────────
log("\n[9] DASHBOARD SUB-ENDPOINTS")
dash_endpoints = [
    "/dashboard/positions", "/dashboard/orders", "/dashboard/portfolio",
    "/dashboard/pnl", "/dashboard/risk", "/dashboard/signals",
    "/dashboard/market-overview", "/dashboard/performance",
    "/dashboard/alerts", "/dashboard/summary"
]
for ep in dash_endpoints:
    sc, data = get(ep, H, label=f"dashboard_{ep.split('/')[-1]}")
    log(f"  {ep} → {sc}: {str(data)[:100]}")

# ── 10. Portfolio ────────────────────────────────────────────────────────────
log("\n[10] PORTFOLIO API")
sc, port = get("/portfolio/", H, label="portfolio")
log(f"  /portfolio/ → {sc}: {str(port)[:200]}")
sc, ph = get("/portfolio/holdings", H, label="portfolio_holdings")
log(f"  /portfolio/holdings → {sc}: {str(ph)[:200]}")

# ── 11. Execution ────────────────────────────────────────────────────────────
log("\n[11] EXECUTION API")
sc, ex = get("/execution/orders", H, label="execution_orders")
log(f"  /execution/orders → {sc}: {str(ex)[:200]}")
sc, ex2 = get("/execution/positions", H, label="execution_positions")
log(f"  /execution/positions → {sc}: {str(ex2)[:200]}")

# ── 12. Market data ───────────────────────────────────────────────────────────
log("\n[12] MARKET DATA API")
sc, mh = get("/market/history?symbol=NIFTY&interval=1d&limit=5", label="market_history")
log(f"  /market/history → {sc}: {str(mh)[:200]}")
sc, mq = get("/market/quote?symbol=NIFTY", label="market_quote")
log(f"  /market/quote → {sc}: {str(mq)[:200]}")

# ── 13. Signal ────────────────────────────────────────────────────────────────
log("\n[13] SIGNAL API")
sc, sig = get("/signal/generate?symbol=NIFTY", label="signal")
log(f"  /signal/generate → {sc}: {str(sig)[:200]}")

# ── 14. Risk ─────────────────────────────────────────────────────────────────
log("\n[14] RISK API")
sc, risk = get("/risk/calculate?symbol=NIFTY&capital=100000&entry=24350&stop_loss=24000", label="risk")
log(f"  /risk/calculate → {sc}: {str(risk)[:200]}")

# ── 15. AI Consensus ────────────────────────────────────────────────────────
log("\n[15] AI CONSENSUS")
ai_payload = {"symbol": "NIFTY", "price": 24350, "indicators": {"rsi": 55, "ema_fast": 24300, "ema_slow": 24100}}
sc, ai_resp = post("/ai/consensus/analyze", ai_payload, label="ai_consensus")
log(f"  /ai/consensus/analyze → {sc}: {str(ai_resp)[:300]}")

# ── 16. Strategy ─────────────────────────────────────────────────────────────
log("\n[16] STRATEGY API")
sc, strat = get("/strategy/list", label="strategy_list")
log(f"  /strategy/list → {sc}: {str(strat)[:200]}")

# ── 17. Trade history (DB) ───────────────────────────────────────────────────
log("\n[17] DB TRADE HISTORY")
sc, th = get("/history/", H, label="history_all")
log(f"  /history/ → {sc}: {str(th)[:200]}")

# ── 18. WebSocket check ──────────────────────────────────────────────────────
log("\n[18] WEBSOCKET")
log("  WebSocket /ws/ticks requires ws:// protocol — testing via HTTP head")
try:
    r = requests.get(f"{BASE}/ws/ticks", timeout=3)
    log(f"  HTTP GET /ws/ticks → {r.status_code} (expects 400/426 upgrade required)")
    results["websocket_endpoint"] = {"status": r.status_code, "note": "WebSocket upgrade needed for full test"}
except Exception as e:
    log(f"  /ws/ticks → {e}")
    results["websocket_endpoint"] = {"error": str(e)}

# ── 19. Backtest ─────────────────────────────────────────────────────────────
log("\n[19] BACKTEST API")
bt_payload = {"symbol": "NIFTY", "strategy": "EMA", "start_date": "2026-01-01", "end_date": "2026-06-01", "capital": 100000}
sc, bt = post("/backtest/run", bt_payload, label="backtest_ema")
log(f"  /backtest/run (EMA) → {sc}: {str(bt)[:300]}")

# ── 20. Indicator ────────────────────────────────────────────────────────────
log("\n[20] INDICATOR API")
sc, ind = get("/indicators/calculate?symbol=NIFTY&indicator=RSI", label="indicator_rsi")
log(f"  /indicators/calculate → {sc}: {str(ind)[:200]}")

# ── 21. Final comparison table ────────────────────────────────────────────────
log("\n" + "=" * 70)
log("INTEGRATION MISMATCH SUMMARY")
log("=" * 70)

mismatches = []

# Check 1: Paper account pnl vs dashboard pnl
paper_realized = acc.get("realized_pnl") if acc else "N/A"
paper_total    = acc.get("total_pnl") if acc else "N/A"
paper_wins     = acc.get("win_count") if acc else "N/A"
paper_trades   = acc.get("trade_count") if acc else "N/A"
dash_realized  = pnl.get("realized_pnl") if dash else "N/A"
dash_total     = pnl.get("total_pnl") if dash else "N/A"
dash_wins      = pnl.get("winning_trades") if dash else "N/A"

log(f"\n  FIELD                    PAPER ENGINE       DASHBOARD API")
log(f"  {'─'*60}")
log(f"  realized_pnl             {str(paper_realized):18} {str(dash_realized)}")
log(f"  total_pnl                {str(paper_total):18} {str(dash_total)}")
log(f"  win_count                {str(paper_wins):18} {str(dash_wins)}")
log(f"  trade_count              {str(paper_trades):18} (dashboard shows broker trades, not paper)")

if paper_realized != dash_realized:
    mismatches.append({
        "id": "MM-01",
        "field": "realized_pnl / total_pnl / win_count",
        "page": "Dashboard",
        "paper_value": paper_realized,
        "dashboard_value": dash_realized,
        "root_cause": "dashboard_service.py fetches PnL from broker API (Angel One), which is disconnected. Paper engine PnL is stored in PaperEngine._account (in-memory singleton) but dashboard_router never reads from it.",
        "affected_files": [
            "app/dashboard/dashboard_service.py",
            "app/dashboard/dashboard_router.py"
        ],
        "fix": "In dashboard_router.py GET /dashboard/, add paper engine account to response when broker is offline: merge paper_engine.get_account() into the pnl section."
    })

results["mismatches"] = mismatches
results["summary"] = {
    "total_trades_attempted": trade_num,
    "trades_success": ok,
    "trades_fail": fail,
    "paper_final": acc,
    "dashboard_pnl": pnl,
    "mismatches_found": len(mismatches),
    "test_time": datetime.datetime.now().isoformat()
}

log(f"\n  Total mismatches found: {len(mismatches)}")
for m in mismatches:
    log(f"\n  [{m['id']}] {m['field']}")
    log(f"    Page: {m['page']}")
    log(f"    Paper value:     {m['paper_value']}")
    log(f"    Dashboard value: {m['dashboard_value']}")
    log(f"    Root cause:      {m['root_cause']}")
    log(f"    Fix:             {m['fix']}")

log("\n" + "=" * 70)
log(f"INTEGRATION TEST COMPLETE — {datetime.datetime.now().strftime('%H:%M:%S')}")
log("=" * 70)

save()
print(f"\nResults saved to:\n  {OUT_JSON}\n  {OUT_TXT}")
