"""
V42 Diagnostic: 52-Week High Proximity Signal

Hypothesis: Stocks trading near their 52-week high on the ORB entry day have:
  (a) confirmed long-term uptrend momentum
  (b) no overhead price resistance (buyers that are "underwater" don't sell)
  (c) higher institutional confidence = better ORB follow-through

Features:
  pct_from_52w_high   : (last_close - 52w_high) / 52w_high [≤ 0; 0 = at high]
  is_near_52w_high    : 1 if within 5% of 52w high, else 0 (binary version)
  dist_from_52w_low   : (last_close - 52w_low) / (52w_high - 52w_low) [0-1, 1 = at high]
                        "position in 52-week range" — independent from proximity to high

Source: V24 400d run JSON + daily parquet cache (need 252 prior trading days).
"""
from __future__ import annotations

import concurrent.futures
import datetime as dt
import json
import os
import sys
from pathlib import Path

import pyarrow.parquet as pq

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")))

from zoneinfo import ZoneInfo
from libs.common.time_utils import trading_days_between

_ET = ZoneInfo("America/New_York")
_MKT_OPEN = dt.time(9, 30)
_MKT_CLOSE = dt.time(16, 0)

INTRADAY_CACHE_DIR = "data/cache/intraday"
V24_400D_RUN = "runs/intraday_orb/intraday_20260422_011012_06f59ede.json"


def _parse_ts(ts_raw: object) -> dt.datetime:
    s = str(ts_raw)
    if s.endswith("Z"):
        s = s[:-1] + "+00:00"
    return dt.datetime.fromisoformat(s).astimezone(_ET)


def get_daily_close(ticker: str, dates: list[str]) -> dict[str, float]:
    closes = {}
    for date in dates:
        path = Path(INTRADAY_CACHE_DIR) / ticker / f"{date}.parquet"
        if not path.exists():
            continue
        try:
            table = pq.read_table(str(path))
            rows = table.to_pydict()
        except Exception:
            continue
        c_list = []
        for i, ts_raw in enumerate(rows.get("timestamp", [])):
            try:
                ts = _parse_ts(ts_raw)
            except Exception:
                continue
            if _MKT_OPEN <= ts.time() < _MKT_CLOSE:
                c_list.append(float(rows["close"][i] or 0))
        if c_list and c_list[-1] > 0:
            closes[date] = c_list[-1]
    return closes


def pearson(xs: list[float], ys: list[float]) -> float | None:
    n = len(xs)
    if n < 2:
        return None
    xm, ym = sum(xs) / n, sum(ys) / n
    num = sum((xs[i] - xm) * (ys[i] - ym) for i in range(n))
    dx = sum((x - xm) ** 2 for x in xs) ** 0.5
    dy = sum((y - ym) ** 2 for y in ys) ** 0.5
    if dx <= 0 or dy <= 0:
        return None
    return num / (dx * dy)


def tercile_stats(vals: list[float], rs: list[float]) -> dict:
    if len(vals) < 9:
        return {}
    pairs = sorted(zip(vals, rs), key=lambda p: p[0])
    n = len(pairs)
    t = n // 3
    def stats(sub):
        ys = [p[1] for p in sub]
        return {"n": len(ys), "wr": sum(1 for y in ys if y > 0) / len(ys), "avg_r": sum(ys) / len(ys)}
    return {"low": stats(pairs[:t]), "mid": stats(pairs[t:2*t]), "high": stats(pairs[2*t:])}


def report_feature(label: str, vals: list[float], rs: list[float]) -> None:
    n = len(vals)
    p = pearson(vals, rs)
    ts = tercile_stats(vals, rs)
    if not ts or p is None:
        print(f"  {label}: n={n}, insufficient data")
        return
    low, mid, high = ts["low"], ts["mid"], ts["high"]
    avg_r_gap = abs(high["avg_r"] - low["avg_r"])
    wr_gap = abs(high["wr"] - low["wr"])
    g1 = abs(p) >= 0.07 and n >= 120
    g2 = avg_r_gap >= 0.30
    g3 = wr_gap >= 0.05
    print(f"\n  [{label}]  n={n}  Pearson={p:+.3f}")
    print(f"    G1: {'PASS' if g1 else 'FAIL'} (|{abs(p):.3f}| {'≥' if abs(p)>=0.07 else '<'} 0.07, n={n})")
    print(f"    G2: {'PASS' if g2 else 'FAIL'} (avg_R gap = {avg_r_gap:.3f}R  [threshold 0.30R])")
    print(f"    G3: {'PASS' if g3 else 'FAIL'} (WR gap = {wr_gap*100:.1f}pp  [threshold 5pp])")
    print(f"    Bottom tercile: WR {low['wr']*100:.1f}%  avg_R {low['avg_r']:+.3f}  n={low['n']}")
    print(f"    Middle tercile: WR {mid['wr']*100:.1f}%  avg_R {mid['avg_r']:+.3f}  n={mid['n']}")
    print(f"    Top tercile:    WR {high['wr']*100:.1f}%  avg_R {high['avg_r']:+.3f}  n={high['n']}")
    print(f"    → {'ALL GATES PASS ✓' if (g1 and g2 and g3) else 'FAIL'}")


def main() -> None:
    print("=== V42 52-Week High Proximity Diagnostic ===\n")

    with open(V24_400D_RUN) as f:
        run_data = json.load(f)
    trades = run_data.get("trades", [])
    m = run_data.get("metrics", {})
    print(f"Loaded V24 400d: {m.get('total_trades')} trades, "
          f"{m.get('start_date')} → {m.get('end_date')}")

    trade_records = [
        {"ticker": t["ticker"], "date": t["date"][:10],
         "r_multiple": float(t["r_multiple_at_exit"])}
        for t in trades
        if t.get("r_multiple_at_exit") is not None
    ]
    tickers_needed = sorted(set(r["ticker"] for r in trade_records))

    min_d = dt.date.fromisoformat(min(r["date"] for r in trade_records))
    max_d = dt.date.fromisoformat(max(r["date"] for r in trade_records))
    # Need 252 prior trading days = ~1 year lookback
    start_d = min_d - dt.timedelta(days=400)
    all_td = trading_days_between(start_d, max_d)
    all_dates = [d.isoformat() for d in all_td]

    print(f"Loading closes for {len(tickers_needed)} tickers across {len(all_dates)} trading days...")

    ticker_closes: dict[str, dict[str, float]] = {}
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as ex:
        def _load(ticker: str) -> tuple[str, dict[str, float]]:
            return ticker, get_daily_close(ticker, all_dates)
        for ticker, closes in ex.map(_load, tickers_needed):
            if closes:
                ticker_closes[ticker] = closes

    print(f"Loaded: {len(ticker_closes)} / {len(tickers_needed)} tickers\n")

    pct_from_high_vals, range_pos_vals, r_mults = [], [], []
    missing = 0

    for rec in trade_records:
        ticker = rec["ticker"]
        date = rec["date"]
        r = rec["r_multiple"]

        closes = ticker_closes.get(ticker, {})
        # Get all closes STRICTLY before trade date
        prev_closes = [(d, c) for d, c in closes.items() if d < date]
        if len(prev_closes) < 50:
            missing += 1
            continue

        # Sort by date and take last 252 trading days
        prev_closes = sorted(prev_closes, key=lambda x: x[0])
        window = prev_closes[-252:]
        prices = [c for _, c in window]
        last_close = prices[-1]
        high_52w = max(prices)
        low_52w = min(prices)

        if high_52w <= 0:
            missing += 1
            continue

        pct_from_high = (last_close - high_52w) / high_52w  # ≤ 0
        if high_52w == low_52w:
            range_pos = 0.5
        else:
            range_pos = (last_close - low_52w) / (high_52w - low_52w)  # [0, 1]

        pct_from_high_vals.append(pct_from_high)
        range_pos_vals.append(range_pos)
        r_mults.append(r)

    n_valid = len(r_mults)
    print(f"Valid trades: {n_valid} / {len(trade_records)}  (missing: {missing})")
    if pct_from_high_vals:
        avg_pfh = sum(pct_from_high_vals) / len(pct_from_high_vals)
        print(f"Mean pct_from_52w_high: {avg_pfh:.1%}")
        print(f"Mean range_pos: {sum(range_pos_vals)/len(range_pos_vals):.2f}")

    print("\n" + "=" * 60)
    print("GATE RESULTS (G1: |P|≥0.07 & n≥120; G2: avg_R≥0.30R; G3: WR≥5pp)")

    report_feature("pct_from_52w_high (0 = AT high, negative = below)", pct_from_high_vals, r_mults)
    report_feature("52w_range_position (0=at_low, 1=at_high)", range_pos_vals, r_mults)

    p_corr = pearson(pct_from_high_vals, range_pos_vals)
    print(f"\n  ρ(pct_from_high, range_pos) = {p_corr:.3f}")

    print("\n=== Summary ===")
    print("Target: G2 ≥ 0.30R. Any value approaching this warrants further investigation.")


if __name__ == "__main__":
    main()
