"""
V48 Phase 0.5 — Binary gate: does prior-180d 13D/13G event presence predict V46 trade R?

Three binary flags tested:
  has_any_13dg     : any filing in prior 180d (PIT-safe: filing_date < entry_date)
  has_13d_active   : any SC 13D / SCHEDULE 13D (active intent, no 13G, no amendments)
  has_initial_entry: any row with is_initial_for_owner=True

Gate (pre-committed):
  PASS      : |ΔavgR| >= 0.30R AND n_minority >= 30 AND |t| >= 1.96
  SOFT-PASS : |ΔavgR| >= 0.20R AND n_minority >= 40 AND |t| >= 2.33
  FAIL      : else → close axis (24th)
"""

from __future__ import annotations

import argparse
import json
import math
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path

import pandas as pd

ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(ROOT))


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _welch_t(a: list[float], b: list[float]) -> float:
    n1, n2 = len(a), len(b)
    if n1 < 2 or n2 < 2:
        return 0.0
    mean1 = sum(a) / n1
    mean2 = sum(b) / n2
    var1 = sum((x - mean1) ** 2 for x in a) / (n1 - 1)
    var2 = sum((x - mean2) ** 2 for x in b) / (n2 - 1)
    se2 = var1 / n1 + var2 / n2
    if se2 <= 0:
        return 0.0
    return (mean1 - mean2) / math.sqrt(se2)


def _win_rate(vals: list[float]) -> float:
    return sum(1 for v in vals if v > 0) / len(vals) if vals else 0.0


def _load_trades(path: str) -> list[dict]:
    with open(path) as f:
        data = json.load(f)
    return data.get("trades", [])


def _load_parquet(path: str) -> pd.DataFrame:
    df = pd.read_parquet(path)
    df["filing_date"] = pd.to_datetime(df["filing_date"]).dt.date
    return df


# ---------------------------------------------------------------------------
# Feature computation
# ---------------------------------------------------------------------------

def _compute_flags(trades: list[dict], df: pd.DataFrame, window_days: int) -> list[dict]:
    # Build per-symbol index of events
    sym_events: dict[str, pd.DataFrame] = {}
    for sym, grp in df.groupby("symbol"):
        sym_events[str(sym)] = grp.reset_index(drop=True)

    results = []
    for t in trades:
        sym = t["ticker"]
        entry_str = t["date"]
        entry_date = datetime.fromisoformat(entry_str).date() if "T" in entry_str else datetime.strptime(entry_str, "%Y-%m-%d").date()
        cutoff = entry_date - timedelta(days=window_days)
        r = t["r_multiple_at_exit"]

        has_any = 0
        has_13d = 0
        has_initial = 0

        if sym in sym_events:
            sub = sym_events[sym]
            # PIT-safe: filing_date strictly before entry_date
            mask = (sub["filing_date"] < entry_date) & (sub["filing_date"] >= cutoff)
            sub_pit = sub[mask]
            if len(sub_pit) > 0:
                has_any = 1
                # Active 13D: form_type contains '13D' but NOT amendment suffix alone
                # SC 13D, SCHEDULE 13D — exclude SC 13D/A and SCHEDULE 13D/A
                ft = sub_pit["form_type"].str.upper()
                is_13d = ft.isin(["SC 13D", "SCHEDULE 13D"])
                if is_13d.sum() > 0:
                    has_13d = 1
                if sub_pit["is_initial_for_owner"].sum() > 0:
                    has_initial = 1

        results.append({
            "ticker": sym,
            "date": entry_str,
            "r": r,
            "has_any_13dg": has_any,
            "has_13d_active": has_13d,
            "has_initial_entry": has_initial,
        })
    return results


# ---------------------------------------------------------------------------
# Gate evaluation
# ---------------------------------------------------------------------------

PASS_DELTA_R = 0.30
PASS_N_MIN = 30
PASS_T = 1.96
SOFT_DELTA_R = 0.20
SOFT_N_MIN = 40
SOFT_T = 2.33


def _evaluate_flag(name: str, rows: list[dict]) -> dict:
    pos = [r["r"] for r in rows if r[name] == 1]
    neg = [r["r"] for r in rows if r[name] == 0]
    n_pos = len(pos)
    n_neg = len(neg)
    avg_pos = sum(pos) / n_pos if pos else 0.0
    avg_neg = sum(neg) / n_neg if neg else 0.0
    delta_r = abs(avg_pos - avg_neg)
    wr_pos = _win_rate(pos)
    wr_neg = _win_rate(neg)
    delta_wr = abs(wr_pos - wr_neg) * 100
    t_stat = abs(_welch_t(pos, neg))
    direction = "+" if avg_pos > avg_neg else "-"

    # Minority group is the smaller (has=1 group)
    n_minority = n_pos

    if (delta_r >= PASS_DELTA_R and n_minority >= PASS_N_MIN and t_stat >= PASS_T):
        verdict = "PASS"
    elif (delta_r >= SOFT_DELTA_R and n_minority >= SOFT_N_MIN and t_stat >= SOFT_T):
        verdict = "SOFT-PASS"
    else:
        verdict = "FAIL"

    return {
        "flag": name,
        "n_minority": n_minority,
        "n_majority": n_neg,
        "avg_r_minority": round(avg_pos, 4),
        "avg_r_majority": round(avg_neg, 4),
        "delta_r": round(delta_r, 4),
        "wr_minority_pct": round(wr_pos * 100, 1),
        "wr_majority_pct": round(wr_neg * 100, 1),
        "delta_wr_pp": round(delta_wr, 1),
        "t_stat": round(t_stat, 3),
        "direction": direction,
        "verdict": verdict,
    }


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--v46-run", default="runs/v46_correct_w12_400d.json/intraday_20260422_043215_63f03e64.json")
    ap.add_argument("--parquet", default="data/reference/ownership_13d13g_events_pit.parquet")
    ap.add_argument("--window-days", type=int, default=180)
    ap.add_argument("--out", default=None)
    args = ap.parse_args()

    os.chdir(ROOT)

    trades = _load_trades(args.v46_run)
    df = _load_parquet(args.parquet)

    print(f"V46 trades: {len(trades)}")
    print(f"13D/G parquet rows: {len(df)}, date range: {df['filing_date'].min()} → {df['filing_date'].max()}")

    # Parquet tail gap warning
    max_trade_date = max(datetime.fromisoformat(t["date"]).date() if "T" in t["date"] else datetime.strptime(t["date"], "%Y-%m-%d").date() for t in trades)
    parquet_max = df["filing_date"].max()
    gap_days = (max_trade_date - parquet_max).days
    if gap_days > 14:
        print(f"  WARN: parquet ends {parquet_max}, last trade {max_trade_date} ({gap_days}d gap) — tail trades may have false-negative flags")

    rows = _compute_flags(trades, df, args.window_days)
    total = len(rows)
    n_any = sum(r["has_any_13dg"] for r in rows)
    n_13d = sum(r["has_13d_active"] for r in rows)
    n_ini = sum(r["has_initial_entry"] for r in rows)
    print(f"\nCoverage (prior {args.window_days}d):")
    print(f"  has_any_13dg    : {n_any}/{total} = {n_any/total*100:.1f}%")
    print(f"  has_13d_active  : {n_13d}/{total} = {n_13d/total*100:.1f}%")
    print(f"  has_initial_entry: {n_ini}/{total} = {n_ini/total*100:.1f}%")

    flags = ["has_any_13dg", "has_13d_active", "has_initial_entry"]
    evaluations = [_evaluate_flag(f, rows) for f in flags]

    print(f"\n{'Flag':<22} {'n_min':>5} {'avgR_min':>9} {'avgR_maj':>9} {'ΔR':>7} {'ΔWR':>6} {'|t|':>6} {'dir':>4} {'Verdict'}")
    print("-" * 90)
    for e in evaluations:
        print(
            f"{e['flag']:<22} {e['n_minority']:>5} {e['avg_r_minority']:>+9.4f} "
            f"{e['avg_r_majority']:>+9.4f} {e['delta_r']:>7.4f} {e['delta_wr_pp']:>6.1f}pp "
            f"{e['t_stat']:>6.3f} {e['direction']:>4}  {e['verdict']}"
        )

    print(f"\nGates: PASS=|ΔR|≥{PASS_DELTA_R}R & n_min≥{PASS_N_MIN} & |t|≥{PASS_T}")
    print(f"       SOFT=|ΔR|≥{SOFT_DELTA_R}R & n_min≥{SOFT_N_MIN} & |t|≥{SOFT_T}")

    verdicts = [e["verdict"] for e in evaluations]
    if "PASS" in verdicts:
        axis_verdict = "ADVANCE_TO_PHASE1"
    elif "SOFT-PASS" in verdicts:
        axis_verdict = "SOFT_ADVANCE_TO_PHASE1"
    else:
        axis_verdict = "CLOSE_AXIS_24"

    print(f"\n>>> PHASE 0.5 VERDICT: {axis_verdict} <<<")
    if axis_verdict in ("CLOSE_AXIS_24",):
        best = max(evaluations, key=lambda e: e["delta_r"])
        print(f"    Best flag: {best['flag']} ΔR={best['delta_r']:.4f}R n={best['n_minority']} |t|={best['t_stat']:.3f}")
        print("    → V48 13D/G axis closed. Route to post-close decision tree (Option A/B/C).")

    result = {
        "axis": "V48_activist_13dg",
        "phase": "0.5_binary_gate",
        "window_days": args.window_days,
        "n_trades": total,
        "coverage": {"has_any_13dg": n_any, "has_13d_active": n_13d, "has_initial_entry": n_ini},
        "evaluations": evaluations,
        "verdict": axis_verdict,
    }

    if args.out:
        Path(args.out).parent.mkdir(parents=True, exist_ok=True)
        with open(args.out, "w") as f:
            json.dump(result, f, indent=2)
        print(f"\nSaved → {args.out}")


if __name__ == "__main__":
    main()
