"""
system_verification_v1.py
==========================
End-to-end verification that every component of the live system
is correctly configured and working before trusting it with real trades.

Checks:
  1. Rate data files — present, fresh, correct format
  2. MT5 connection — connected, correct account, balance correct
  3. Signal pipeline — z-score computes correctly from MT5 bars
  4. Position sizing — lot size calculates correctly for each z-band
  5. Order logic — TP and SL levels calculate correctly
  6. Fast poll — ARMED state initialises and clears correctly
  7. 52h hold logic — correctly tracks time, does not block new signals
  8. Position close detection — STATE resets when position closes
  9. Rate update logic — ECB fallback present in auto_rates_loader
 10. Entry method — Fib 0.786 is configured, not market entry

Run on VPS to verify the live system end-to-end:
  python src/research/system_verification_v1.py

Expected: all checks PASS. Any FAIL needs investigating before live trading.
"""

import sys
import os
from pathlib import Path
from datetime import datetime, timezone, timedelta
import pandas as pd
import numpy as np

BASE_PATH = Path(__file__).resolve().parents[2]
SRC_PATH  = BASE_PATH / "src"
if str(SRC_PATH) not in sys.path:
    sys.path.append(str(SRC_PATH))

RATES_DIR  = BASE_PATH / "data" / "raw" / "rates"
TRADES_DIR = BASE_PATH / "data" / "processed" / "trades"

RESULTS = []

def check(name, passed, detail="", warn=False):
    status = "PASS" if passed else ("WARN" if warn else "FAIL")
    RESULTS.append((name, status, detail))
    icon = "✓" if passed else ("⚠" if warn else "✗")
    print(f"  {icon} {name:<45} {status:<6}  {detail}")
    return passed


def section(title):
    print(f"\n  {'─'*65}")
    print(f"  {title}")
    print(f"  {'─'*65}")


def main():
    print("=" * 70)
    print("SYSTEM VERIFICATION  —  End-to-End Live System Check")
    print(f"  Run at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("=" * 70)

    # ── 1. Rate data files ────────────────────────────────────────────────────
    section("1. RATE DATA FILES")

    for fname, col in [("us2y.csv","us2y"), ("us10y.csv","us10y"),
                        ("de2y.csv","de2y"), ("de10y.csv","de10y")]:
        path = RATES_DIR / fname
        if not path.exists():
            check(f"{fname} exists", False, "File not found")
            continue

        df = pd.read_csv(path)
        df.columns = [c.lower() for c in df.columns]
        df["date"] = pd.to_datetime(df["date"], errors="coerce")
        last_date  = df["date"].max()
        days_old   = (pd.Timestamp.now() - last_date).days

        check(f"{fname} exists", True, f"{len(df):,} rows")
        check(f"{fname} freshness",
              days_old <= 3,
              f"Latest: {last_date.date()} ({days_old} days old)",
              warn=(3 < days_old <= 5))
        check(f"{fname} no NaNs in last 30 rows",
              df[col].tail(30).notna().all(),
              f"Last value: {df[col].iloc[-1]:.3f}")

    # ── 2. MT5 connection ─────────────────────────────────────────────────────
    section("2. MT5 CONNECTION")

    try:
        import MetaTrader5 as mt5
        mt5_available = True
        check("MetaTrader5 package installed", True)
    except ImportError:
        check("MetaTrader5 package installed", False, "pip install MetaTrader5")
        mt5_available = False

    if mt5_available:
        if mt5.initialize():
            info = mt5.account_info()
            if info:
                check("MT5 connected", True, f"Account: {info.login}")
                check("MT5 balance correct",
                      info.balance > 0,
                      f"Balance: ${info.balance:,.2f}")
                check("MT5 algo trading enabled",
                      True,  # can't check programmatically but script ran
                      "Assumed enabled (script is running)")

                # Check EURUSD symbol
                sym = mt5.symbol_info("EURUSD")
                check("EURUSD symbol visible",
                      sym is not None and sym.visible,
                      "EURUSD in market watch")

                # Check we can get bars
                bars = mt5.copy_rates_from_pos("EURUSD", mt5.TIMEFRAME_H1, 0, 10)
                check("MT5 1H bars accessible",
                      bars is not None and len(bars) > 0,
                      f"{len(bars) if bars is not None else 0} bars returned")

                bars_15m = mt5.copy_rates_from_pos("EURUSD", mt5.TIMEFRAME_M15, 0, 10)
                check("MT5 15M bars accessible",
                      bars_15m is not None and len(bars_15m) > 0,
                      f"{len(bars_15m) if bars_15m is not None else 0} bars returned")
            else:
                check("MT5 account info", False, "Could not get account info")
        else:
            check("MT5 connected", False, f"Error: {mt5.last_error()}")

    # ── 3. Signal pipeline ────────────────────────────────────────────────────
    section("3. SIGNAL PIPELINE")

    try:
        exec_path = BASE_PATH / "src" / "execution" / "live_signal_monitor.py"
        spec = {}
        with open(exec_path) as f:
            src = f.read()

        # Check key parameters in script
        check("Threshold = 2.75",
              "THRESHOLD         = 2.75" in src or "THRESHOLD = 2.75" in src,
              "z-score threshold correct")
        check("TP = 0.20%",
              "TP_PCT            = 0.0020" in src or "TP_PCT = 0.0020" in src,
              "take profit correct")
        check("Stop = 0.25%",
              "STOP_PCT          = 0.0025" in src or "STOP_PCT = 0.0025" in src,
              "stop loss correct")
        check("Z-exit = 1.5",
              "ZSCORE_EXIT       = 1.5" in src or "ZSCORE_EXIT = 1.5" in src,
              "z-score exit threshold correct")
        check("Hold = 52h",
              "HOLD_HOURS        = 52" in src or "HOLD_HOURS = 52" in src,
              "maximum hold time correct")
        check("Base risk = 0.75%",
              "BASE_RISK_PCT     = 0.0075" in src or "BASE_RISK_PCT = 0.0075" in src,
              "risk per trade correct")
        check("Fib = 0.786",
              "FIB               = 0.786" in src or "FIB = 0.786" in src,
              "Fibonacci level correct")
        check("Bars = 3500",
              "BARS_1H       = 3500" in src or "3500" in src,
              "sufficient bars for beta window")
        check("Session 7-16 EET",
              "SESSION_START_EET = 7" in src and "SESSION_END_EET   = 16" in src,
              "London/NY session window correct")

        # Check fast polling is implemented
        check("Fast polling implemented",
              "ARMED" in src and "run_fast_poll" in src,
              "5-second entry detection active")
        check("Position close detection",
              "Always check position status every 5 seconds" in src,
              "immediate close detection in main loop")
        check("52h as max hold not blocker",
              "hours_held >= 52" in src and "last_exit" not in src,
              "52h closes trade, does not block new signals")
        check("ECB fallback in auto_rates_loader",
              "_fetch_from_ecb" in open(
                  BASE_PATH/"src"/"ingestion"/"auto_rates_loader.py"
              ).read(),
              "DE rates have Bundesbank + ECB fallback")
        check("Rate update on startup",
              "Running startup rate data update" in src,
              "rates update every time script starts")
        check("Daily rate schedule",
              '06:00' in src and 'update_rate_data' in src,
              "rates update daily at 06:00 UTC independent of session")

    except Exception as e:
        check("Script parameter check", False, str(e)[:60])

    # ── 4. Position sizing ────────────────────────────────────────────────────
    section("4. POSITION SIZING LOGIC")

    account_balance = 100_000.0
    stop_pct  = 0.0025
    risk_pct  = 0.0075
    pip_value = 10.0   # per standard lot on EURUSD

    for z, expected_mult, band_name in [
        (2.80, 1.0, "Band 1 (z 2.75-3.50)"),
        (3.60, 1.5, "Band 2 (z 3.50-4.50)"),
        (4.60, 2.0, "Band 3 (z 4.50+)"),
    ]:
        risk_dollar = account_balance * risk_pct * expected_mult
        stop_pips   = stop_pct / 0.0001
        lot_size    = risk_dollar / (stop_pips * pip_value)
        lot_rounded = round(lot_size / 0.01) * 0.01

        check(f"Lot size {band_name}",
              0.5 <= lot_rounded <= 10.0,
              f"z={z} → {expected_mult}x → {lot_rounded:.2f} lots "
              f"(risk=${risk_dollar:,.0f})")

    # ── 5. TP and SL levels ───────────────────────────────────────────────────
    section("5. TP AND SL LEVELS (example at EURUSD 1.1500)")

    price = 1.1500
    tp_pct   = 0.0020
    stop_pct = 0.0025

    long_tp   = round(price * (1 + tp_pct), 5)
    long_sl   = round(price * (1 - stop_pct), 5)
    long_tp_p = round((long_tp - price) / 0.0001, 1)
    long_sl_p = round((price - long_sl) / 0.0001, 1)

    check("Long TP = +20 pips approx",
          18 <= long_tp_p <= 24,
          f"Entry: {price} | TP: {long_tp} (+{long_tp_p:.1f} pips)")
    check("Long SL = -25 pips approx",
          23 <= long_sl_p <= 29,
          f"Entry: {price} | SL: {long_sl} (-{long_sl_p:.1f} pips)")
    check("Risk:Reward ratio",
          True,
          f"RR = {long_sl_p/long_tp_p:.2f}:1  (need 75% WR to be profitable)")

    # ── 6. Signal z-score live check ─────────────────────────────────────────
    section("6. LIVE Z-SCORE COMPUTATION")

    if mt5_available and mt5.initialize():
        try:
            # Get 3500 1H bars
            rates = mt5.copy_rates_from_pos("EURUSD", mt5.TIMEFRAME_H1, 0, 3500)
            bars  = pd.DataFrame(rates)
            bars["datetime"] = pd.to_datetime(bars["time"], unit="s")
            bars["date"]     = bars["datetime"].dt.normalize()
            bars             = bars.rename(columns={"tick_volume":"volume"})

            # Quick z-score calculation
            daily = (bars.groupby("date", as_index=False)
                     .agg(close=("close","last"))
                     .sort_values("date").reset_index(drop=True))
            daily["ret_1d"] = daily["close"].pct_change()

            us2y = pd.read_csv(RATES_DIR/"us2y.csv")
            de2y = pd.read_csv(RATES_DIR/"de2y.csv")
            us2y["date"] = pd.to_datetime(us2y["date"])
            de2y["date"] = pd.to_datetime(de2y["date"])
            daily["date"] = pd.to_datetime(daily["date"])

            merged = daily.merge(
                us2y[["date","us2y"]], on="date", how="left"
            ).merge(
                de2y[["date","de2y"]], on="date", how="left"
            ).ffill()
            merged["spread"] = merged["us2y"] - merged["de2y"]
            merged["spread_chg"] = merged["spread"].diff()

            # Simple OLS beta
            from scipy import stats
            sample = merged.dropna(subset=["ret_1d","spread_chg"]).tail(120)
            if len(sample) >= 30:
                slope, intercept, r, p, se = stats.linregress(
                    sample["spread_chg"], sample["ret_1d"]
                )
                merged["predicted"] = intercept + slope * merged["spread_chg"]
                merged["return_24h"] = merged["close"].pct_change(24)
                merged_h = bars.merge(
                    merged[["date","predicted","return_24h"]],
                    on="date", how="left"
                ).ffill()
                merged_h["lag_gap"] = merged_h["predicted"] - merged_h["return_24h"].fillna(0)
                lag_mean = merged_h["lag_gap"].rolling(60, min_periods=60).mean()
                lag_std  = merged_h["lag_gap"].rolling(60, min_periods=60).std()
                merged_h["zscore"] = (merged_h["lag_gap"] - lag_mean) / lag_std
                zscore_df = merged_h.dropna(subset=["zscore"])

                if not zscore_df.empty:
                    current_z = float(zscore_df["zscore"].iloc[-1])
                    check("Z-score computes successfully",
                          True,
                          f"Current z-score: {current_z:.3f} (threshold ±2.75)")
                    check("Z-score in reasonable range",
                          abs(current_z) < 10,
                          f"|z| = {abs(current_z):.3f} — looks normal")
                    check("Z-score below threshold (no signal now)",
                          abs(current_z) < 2.75,
                          f"{'Signal ACTIVE' if abs(current_z)>=2.75 else 'No signal'}")
            else:
                check("Z-score computation", False, "Insufficient data for beta")
        except Exception as e:
            check("Z-score computation", False, str(e)[:60])

    # ── 7. FRED API key ───────────────────────────────────────────────────────
    section("7. ENVIRONMENT AND CONFIGURATION")

    fred_key = os.getenv("FRED_API_KEY")
    check("FRED_API_KEY set",
          bool(fred_key),
          f"Key: {fred_key[:8]}..." if fred_key else "NOT SET — rates won't update")

    check("Project root correct",
          (BASE_PATH / "src" / "execution" / "live_signal_monitor.py").exists(),
          str(BASE_PATH))
    check("Auto rates loader exists",
          (BASE_PATH / "src" / "ingestion" / "auto_rates_loader.py").exists())
    check("Live monitor exists",
          (BASE_PATH / "src" / "execution" / "live_signal_monitor.py").exists())
    check("Log directory exists",
          (BASE_PATH / "data" / "logs").exists())

    # ── Summary ───────────────────────────────────────────────────────────────
    print(f"\n{'='*70}")
    print("VERIFICATION SUMMARY")
    print(f"{'='*70}")

    passed = sum(1 for _,s,_ in RESULTS if s == "PASS")
    warned = sum(1 for _,s,_ in RESULTS if s == "WARN")
    failed = sum(1 for _,s,_ in RESULTS if s == "FAIL")
    total  = len(RESULTS)

    print(f"\n  Total checks : {total}")
    print(f"  Passed       : {passed}")
    print(f"  Warnings     : {warned}")
    print(f"  Failed       : {failed}")

    if failed == 0 and warned == 0:
        print(f"""
  ✓ ALL CHECKS PASSED
  The live system is correctly configured and ready for trading.
  Expected performance:
    Trades per week : ~3.5
    Win rate        : ~75%
    Phase 1 target  : ~3.4 months median
    Max drawdown    : ~-2.5%
""")
    elif failed == 0:
        print(f"""
  ⚠ ALL CRITICAL CHECKS PASSED — {warned} WARNING(S)
  System can trade but review warnings above.
""")
    else:
        print(f"""
  ✗ {failed} CHECK(S) FAILED — DO NOT TRADE LIVE UNTIL RESOLVED
  Address the failed checks above before running on real capital.
""")

    if mt5_available:
        mt5.shutdown()


if __name__ == "__main__":
    main()
