"""
Punto 1 della roadmap: scarica lo storico partite + quote da football-data.co.uk
e produce un dataset unico e pulito pronto per il modello base e il backtesting.

Uso:
    python scripts/download_data.py                          # default: 6 leghe, stagioni 2017/18 -> 2025/26
    python scripts/download_data.py --leagues I1 E0          # solo Serie A e Premier League
    python scripts/download_data.py --seasons 2223 2324 2425 # stagioni specifiche
    python scripts/download_data.py --force                  # ri-scarica anche i file già in cache

Output:
    data/raw/football-data/<season>_<div>.csv   (CSV originali, cache)
    data/processed/matches.csv                  (dataset unico normalizzato)
"""

import argparse
import sys
import time
from pathlib import Path

import numpy as np
import pandas as pd
import requests

BASE_URL = "https://www.football-data.co.uk/mmz4281/{season}/{div}.csv"

PROJECT_ROOT = Path(__file__).resolve().parent.parent
RAW_DIR = PROJECT_ROOT / "data" / "raw" / "football-data"
PROCESSED_DIR = PROJECT_ROOT / "data" / "processed"

# Codici lega di football-data.co.uk
LEAGUES = {
    "I1": "Serie A",
    "I2": "Serie B",
    "E0": "Premier League",
    "SP1": "La Liga",
    "D1": "Bundesliga",
    "F1": "Ligue 1",
}

# Stagioni: "1718" = 2017/18. Dal 2019/20 in poi ci sono anche le quote di
# chiusura (colonne *C*), il benchmark più forte per il backtesting.
DEFAULT_SEASONS = ["1718", "1819", "1920", "2021", "2122", "2223", "2324", "2425", "2526"]

# Colonne che teniamo nel dataset finale (se presenti nel CSV di origine).
# Riferimento: https://www.football-data.co.uk/notes.txt
KEEP_COLUMNS = [
    # Identificazione partita
    "Div", "Date", "Time", "HomeTeam", "AwayTeam",
    # Risultato
    "FTHG", "FTAG", "FTR", "HTHG", "HTAG", "HTR",
    # Statistiche di gioco
    "HS", "AS", "HST", "AST", "HC", "AC", "HF", "AF", "HY", "AY", "HR", "AR",
    # Quote pre-match: Bet365, Pinnacle, media e massimo di mercato
    "B365H", "B365D", "B365A", "PSH", "PSD", "PSA",
    "AvgH", "AvgD", "AvgA", "MaxH", "MaxD", "MaxA",
    # Quote di CHIUSURA (closing): il benchmark da battere
    "B365CH", "B365CD", "B365CA", "PSCH", "PSCD", "PSCA",
    "AvgCH", "AvgCD", "AvgCA", "MaxCH", "MaxCD", "MaxCA",
    # Over/Under 2.5 (utile più avanti per altri mercati)
    "B365>2.5", "B365<2.5", "Avg>2.5", "Avg<2.5",
]


def download_csv(season: str, div: str, force: bool = False) -> Path | None:
    """Scarica un CSV (con cache locale) e restituisce il path, o None se fallisce."""
    RAW_DIR.mkdir(parents=True, exist_ok=True)
    dest = RAW_DIR / f"{season}_{div}.csv"
    if dest.exists() and not force:
        print(f"  [cache] {dest.name}")
        return dest

    url = BASE_URL.format(season=season, div=div)
    for attempt in range(3):
        try:
            resp = requests.get(url, timeout=30)
            if resp.status_code == 404:
                print(f"  [404]   {season}/{div} non esiste, salto")
                return None
            resp.raise_for_status()
            dest.write_bytes(resp.content)
            print(f"  [ok]    {dest.name} ({len(resp.content) // 1024} KB)")
            return dest
        except requests.RequestException as exc:
            print(f"  [retry {attempt + 1}/3] {url}: {exc}")
            time.sleep(2 * (attempt + 1))
    print(f"  [FAIL]  {url}")
    return None


def load_csv(path: Path, season: str) -> pd.DataFrame | None:
    """Legge un CSV grezzo e lo normalizza alle colonne KEEP_COLUMNS."""
    try:
        # Stagioni recenti: UTF-8 con BOM; stagioni vecchie: latin-1.
        # Il BOM letto come latin-1 corromperebbe il nome della prima colonna (Div).
        encoding = "utf-8-sig" if path.read_bytes()[:3] == b"\xef\xbb\xbf" else "latin-1"
        df = pd.read_csv(path, encoding=encoding, on_bad_lines="skip")
        df.columns = df.columns.str.strip()
    except Exception as exc:
        print(f"  [ERRORE lettura] {path.name}: {exc}")
        return None

    df = df.dropna(subset=["Date", "HomeTeam", "AwayTeam"], how="any")
    if df.empty:
        return None

    # Tiene solo le colonne note, aggiungendo come NaN quelle mancanti
    out = pd.DataFrame(index=df.index)
    for col in KEEP_COLUMNS:
        out[col] = df[col] if col in df.columns else np.nan

    out["Season"] = f"20{season[:2]}/{season[2:]}"
    # Date: dd/mm/yy nelle stagioni vecchie, dd/mm/yyyy in quelle recenti
    out["Date"] = pd.to_datetime(out["Date"], dayfirst=True, format="mixed", errors="coerce")
    out = out.dropna(subset=["Date"])
    return out


def add_implied_probabilities(df: pd.DataFrame) -> pd.DataFrame:
    """
    Converte le quote in probabilità implicite normalizzate (senza margine del
    bookmaker). Priorità: Pinnacle closing > media closing > Bet365 closing >
    Pinnacle pre-match > media pre-match > Bet365 pre-match.
    """
    priority = [
        ("PSCH", "PSCD", "PSCA", "pinnacle_closing"),
        ("AvgCH", "AvgCD", "AvgCA", "avg_closing"),
        ("B365CH", "B365CD", "B365CA", "b365_closing"),
        ("PSH", "PSD", "PSA", "pinnacle"),
        ("AvgH", "AvgD", "AvgA", "avg"),
        ("B365H", "B365D", "B365A", "b365"),
    ]

    prob_h = pd.Series(np.nan, index=df.index)
    prob_d = pd.Series(np.nan, index=df.index)
    prob_a = pd.Series(np.nan, index=df.index)
    source = pd.Series(pd.NA, index=df.index, dtype="string")

    for col_h, col_d, col_a, name in priority:
        odds_h = pd.to_numeric(df[col_h], errors="coerce")
        odds_d = pd.to_numeric(df[col_d], errors="coerce")
        odds_a = pd.to_numeric(df[col_a], errors="coerce")
        valid = odds_h.gt(1) & odds_d.gt(1) & odds_a.gt(1) & prob_h.isna()
        if not valid.any():
            continue
        raw_h, raw_d, raw_a = 1 / odds_h, 1 / odds_d, 1 / odds_a
        overround = raw_h + raw_d + raw_a  # tipicamente 1.02-1.08
        prob_h[valid] = (raw_h / overround)[valid]
        prob_d[valid] = (raw_d / overround)[valid]
        prob_a[valid] = (raw_a / overround)[valid]
        source[valid] = name

    df["ImpProbH"] = prob_h.round(4)
    df["ImpProbD"] = prob_d.round(4)
    df["ImpProbA"] = prob_a.round(4)
    df["ImpProbSource"] = source
    return df


def main() -> int:
    parser = argparse.ArgumentParser(description="Scarica storico partite + quote da football-data.co.uk")
    parser.add_argument("--leagues", nargs="+", default=list(LEAGUES), choices=list(LEAGUES),
                        metavar="DIV", help=f"Codici lega (default: {' '.join(LEAGUES)})")
    parser.add_argument("--seasons", nargs="+", default=DEFAULT_SEASONS, metavar="SSEE",
                        help="Stagioni in formato '2324' per 2023/24 (default: 2017/18 -> 2025/26)")
    parser.add_argument("--force", action="store_true", help="Ri-scarica i file già in cache")
    args = parser.parse_args()

    print(f"Download: {len(args.leagues)} leghe x {len(args.seasons)} stagioni")
    frames = []
    for season in args.seasons:
        for div in args.leagues:
            path = download_csv(season, div, force=args.force)
            if path is None:
                continue
            df = load_csv(path, season)
            if df is not None:
                frames.append(df)

    if not frames:
        print("Nessun dato scaricato.")
        return 1

    matches = pd.concat(frames, ignore_index=True)
    matches = matches.sort_values(["Date", "Div"]).reset_index(drop=True)
    matches = add_implied_probabilities(matches)

    PROCESSED_DIR.mkdir(parents=True, exist_ok=True)
    out_path = PROCESSED_DIR / "matches.csv"
    matches.to_csv(out_path, index=False)

    # Riepilogo
    print("\n=== Riepilogo dataset ===")
    print(f"Partite totali:      {len(matches)}")
    print(f"Periodo:             {matches['Date'].min().date()} -> {matches['Date'].max().date()}")
    print(f"Con quote (1X2):     {matches['ImpProbH'].notna().sum()} "
          f"({matches['ImpProbH'].notna().mean():.1%})")
    closing = matches["ImpProbSource"].isin(["pinnacle_closing", "avg_closing", "b365_closing"])
    print(f"Con quote closing:   {closing.sum()} ({closing.mean():.1%})")
    print("\nPartite per lega:")
    for div, count in matches["Div"].value_counts().items():
        print(f"  {LEAGUES.get(div, div):<16} {count}")
    print(f"\nSalvato in: {out_path}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
