"""
Test "quote di apertura": il modello aggiunge valore rispetto alle quote
PRE-MATCH (quelle a cui si può realmente scommettere in anticipo)?

Il blend contro le quote di chiusura ha dato peso 0: il mercato a fine corsa
sa già tutto quello che sa il modello. Ma chi scommette non gioca alle quote
di chiusura: gioca a quelle disponibili giorni/ore prima. Qui misuriamo:

  1. quanto le quote pre-match sono più "deboli" di quelle di chiusura
  2. se blend(modello + pre-match) batte le pre-match da sole
  3. quanto del divario pre-match -> chiusura il modello riesce a colmare

Protocollo identico a blend.py: peso appreso sul train (2024/25), valutazione
out-of-sample sul test (2025/26), bootstrap a coppie per la significatività.

Uso:
    python scripts/blend_opening.py
"""

import argparse
import sys
from pathlib import Path

import numpy as np
import pandas as pd
import pymysql

import config

from blend import (RESULT_INDEX, bootstrap_diff, fit_weight, geometric_blend,
                   logloss, normalize)

PROJECT_ROOT = Path(__file__).resolve().parent.parent
BT_PATH = PROJECT_ROOT / "data" / "processed" / "backtest_base.csv"
OUT_PATH = PROJECT_ROOT / "data" / "processed" / "backtest_blend_opening.csv"


def load_prematch_probs(args) -> pd.DataFrame:
    """Probabilità implicite dalle quote pre-match (priorità: media > Pinnacle > B365)."""
    conn = pymysql.connect(host=args.host, port=args.port, user=args.user,
                           password=args.password, database="tigertips")
    cur = conn.cursor()
    cur.execute("""
        SELECT m.match_date, h.name, a.name,
               m.avg_home, m.avg_draw, m.avg_away,
               m.pinnacle_home, m.pinnacle_draw, m.pinnacle_away,
               m.b365_home, m.b365_draw, m.b365_away
        FROM matches m
        JOIN teams h ON h.id = m.home_team_id
        JOIN teams a ON a.id = m.away_team_id
    """)
    df = pd.DataFrame(cur.fetchall(), columns=[
        "date", "home", "away",
        "avg_h", "avg_d", "avg_a", "ps_h", "ps_d", "ps_a", "b365_h", "b365_d", "b365_a"])
    conn.close()
    df["date"] = pd.to_datetime(df["date"])

    prob = pd.DataFrame(np.nan, index=df.index, columns=["open_h", "open_d", "open_a"])
    for h, d, a in [("avg_h", "avg_d", "avg_a"),
                    ("ps_h", "ps_d", "ps_a"),
                    ("b365_h", "b365_d", "b365_a")]:
        oh = pd.to_numeric(df[h], errors="coerce")
        od = pd.to_numeric(df[d], errors="coerce")
        oa = pd.to_numeric(df[a], errors="coerce")
        valid = oh.gt(1) & od.gt(1) & oa.gt(1) & prob["open_h"].isna()
        overround = 1 / oh + 1 / od + 1 / oa
        prob.loc[valid, "open_h"] = (1 / oh / overround)[valid]
        prob.loc[valid, "open_d"] = (1 / od / overround)[valid]
        prob.loc[valid, "open_a"] = (1 / oa / overround)[valid]

    return pd.concat([df[["date", "home", "away"]], prob], axis=1)


def main() -> int:
    parser = argparse.ArgumentParser(description="Blend modello + quote pre-match")
    parser.add_argument("--train-seasons", nargs="+", default=["2024/25"])
    parser.add_argument("--test-seasons", nargs="+", default=["2025/26"])
    config.add_db_args(parser)
    args = parser.parse_args()

    bt = pd.read_csv(BT_PATH, parse_dates=["date"])
    prematch = load_prematch_probs(args)
    bt = bt.merge(prematch, on=["date", "home", "away"], how="left")
    bt = bt.dropna(subset=["mkt_h", "mkt_d", "mkt_a", "open_h", "open_d", "open_a"])

    train = bt[bt["season"].isin(args.train_seasons)]
    test = bt[bt["season"].isin(args.test_seasons)]
    print(f"Train: {len(train)} partite {args.train_seasons} | "
          f"Test: {len(test)} partite {args.test_seasons}")

    def unpack(df):
        return (df[["model_h", "model_d", "model_a"]].to_numpy(),
                df[["open_h", "open_d", "open_a"]].to_numpy(),
                df[["mkt_h", "mkt_d", "mkt_a"]].to_numpy(),
                df["result"].map(RESULT_INDEX).to_numpy())

    pm_tr, po_tr, _, y_tr = unpack(train)
    pm_te, po_te, pc_te, y_te = unpack(test)

    # Peso del modello rispetto alle quote pre-match, appreso sul train
    w = fit_weight(geometric_blend, pm_tr, po_tr, y_tr)
    print(f"Peso modello vs quote pre-match (train): w = {w:.3f}")

    p_blend = geometric_blend(pm_te, po_te, w)
    ll_open = logloss(po_te, y_te)
    ll_close = logloss(pc_te, y_te)
    ll_blend = logloss(p_blend, y_te)
    ll_model = logloss(pm_te, y_te)

    print(f"\n=== Test {args.test_seasons} ({len(test)} partite) ===")
    print(f"{'':<26}{'log loss':>10}")
    print(f"{'Mercato closing':<26}{ll_close:>10.4f}")
    print(f"{'Mercato pre-match':<26}{ll_open:>10.4f}")
    print(f"{'Modello DC':<26}{ll_model:>10.4f}")
    print(f"{'Blend modello+pre-match':<26}{ll_blend:>10.4f}")

    mean_diff, lo, hi = bootstrap_diff(p_blend, po_te, y_te)
    verdict = ("il blend BATTE le quote pre-match" if hi < 0 else
               "le quote pre-match battono il blend" if lo > 0 else
               "differenza NON significativa")
    print(f"\nDelta log loss blend - pre-match: {mean_diff:+.4f} "
          f"[IC 95%: {lo:+.4f}, {hi:+.4f}]  ->  {verdict}")

    gap_total = ll_open - ll_close
    gap_closed = ll_open - ll_blend
    if gap_total > 0:
        print(f"Divario pre-match -> closing: {gap_total:.4f}; "
              f"il modello ne colma {gap_closed:.4f} ({gap_closed / gap_total:.0%})")

    print("\nPer lega (test, blend vs pre-match):")
    test = test.copy()
    test[["blend_h", "blend_d", "blend_a"]] = p_blend
    for lg, grp in test.groupby("league"):
        y = grp["result"].map(RESULT_INDEX).to_numpy()
        ll_b = logloss(grp[["blend_h", "blend_d", "blend_a"]].to_numpy(), y)
        ll_o = logloss(grp[["open_h", "open_d", "open_a"]].to_numpy(), y)
        print(f"  {lg:<5} blend {ll_b:.4f}   pre-match {ll_o:.4f}   gap {ll_b - ll_o:+.4f}")

    test.to_csv(OUT_PATH, index=False)
    print(f"\nSalvato in: {OUT_PATH}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
