#!/usr/bin/env python3
from __future__ import annotations

import json
import os
import re
import time
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Optional, Tuple

import numpy as np
import pandas as pd
import requests

REPO_ROOT = Path(__file__).resolve().parents[1]
OUT_DIR = REPO_ROOT / 'artifacts' / 'r1_api_2023_2025'
LEAGUE_ID = 235  # Russia Premier League
SEASONS = [2023, 2024, 2025]


def parse_total_value(raw: str | None) -> Optional[Tuple[str, float]]:
    m = re.match(r'^(Over|Under)\s+([0-9]+(?:\.[0-9]+)?)$', str(raw or '').strip(), re.I)
    if not m:
        return None
    return m.group(1).lower(), float(m.group(2))


def choose_main_total(bookmakers: List[dict]) -> Optional[dict]:
    per_line: Dict[float, dict] = {}
    for bookmaker in bookmakers:
        for bet in bookmaker.get('bets', []):
            if bet.get('name') != 'Goals Over/Under':
                continue
            local: Dict[float, dict] = defaultdict(dict)
            for item in bet.get('values', []):
                parsed = parse_total_value(item.get('value'))
                if not parsed:
                    continue
                side, line = parsed
                try:
                    odd = float(item.get('odd'))
                except Exception:
                    continue
                if odd <= 1:
                    continue
                local[line][side] = odd
            for line, sides in local.items():
                if 'over' not in sides or 'under' not in sides:
                    continue
                rec = per_line.setdefault(line, {'quotes': 0, 'over': [], 'under': []})
                rec['quotes'] += 1
                rec['over'].append(float(sides['over']))
                rec['under'].append(float(sides['under']))
    candidates = []
    for line, rec in per_line.items():
        over_odds = float(np.mean(rec['over']))
        under_odds = float(np.mean(rec['under']))
        over_prob = 1.0 / over_odds
        under_prob = 1.0 / under_odds
        fair_over = over_prob / (over_prob + under_prob) if (over_prob + under_prob) > 0 else np.nan
        candidates.append({
            'ou_line': float(line),
            'over_odds': over_odds,
            'under_odds': under_odds,
            'ou_quote_count': int(rec['quotes']),
            'fair_over_open': fair_over,
            'even_gap': abs((fair_over if not pd.isna(fair_over) else 0.5) - 0.5),
        })
    if not candidates:
        return None
    candidates.sort(key=lambda x: (-x['ou_quote_count'], x['even_gap'], abs(x['ou_line'] - 2.5)))
    chosen = candidates[0].copy()
    chosen.pop('even_gap', None)
    return chosen


def choose_main_1x2(bookmakers: List[dict]) -> dict:
    rows = []
    for bookmaker in bookmakers:
        for bet in bookmaker.get('bets', []):
            if bet.get('name') != 'Match Winner':
                continue
            values = {v.get('value'): v.get('odd') for v in bet.get('values', [])}
            try:
                home = float(values['Home'])
                draw = float(values['Draw'])
                away = float(values['Away'])
            except Exception:
                continue
            if min(home, draw, away) <= 1:
                continue
            rows.append((home, draw, away))
    if not rows:
        return {'home_odds': np.nan, 'draw_odds': np.nan, 'away_odds': np.nan}
    arr = np.array(rows, dtype=float)
    return {
        'home_odds': float(arr[:, 0].mean()),
        'draw_odds': float(arr[:, 1].mean()),
        'away_odds': float(arr[:, 2].mean()),
    }


def fetch_json(session: requests.Session, base_url: str, endpoint: str, params: dict, sleep_seconds: float = 0.15) -> dict:
    if sleep_seconds > 0:
        time.sleep(sleep_seconds)
    r = session.get(base_url + endpoint, params=params, timeout=120)
    r.raise_for_status()
    return r.json()


def collect() -> None:
    base_url = os.environ['API_FOOTBALL_BASE_URL'].rstrip('/')
    api_key = os.environ['API_FOOTBALL_KEY']
    session = requests.Session()
    session.headers.update({'x-apisports-key': api_key})

    OUT_DIR.mkdir(parents=True, exist_ok=True)

    fixtures_rows = []
    for season in SEASONS:
        payload = fetch_json(session, base_url, '/fixtures', {'league': LEAGUE_ID, 'season': season}, sleep_seconds=0.0)
        response = payload.get('response', [])
        print(f'[fixtures] season={season} rows={len(response)}')
        for item in response:
            fixtures_rows.append({
                'season': season,
                'fixture_id': int(item['fixture']['id']),
                'date_utc': item['fixture']['date'],
                'date': str(item['fixture']['date'])[:10],
                'timestamp': int(item['fixture']['timestamp']),
                'status': item['fixture']['status']['short'],
                'round': item['league'].get('round'),
                'home_team': item['teams']['home']['name'],
                'away_team': item['teams']['away']['name'],
                'home_goals': item['goals']['home'],
                'away_goals': item['goals']['away'],
            })
    fixtures_df = pd.DataFrame(fixtures_rows).sort_values(['season', 'timestamp', 'fixture_id']).reset_index(drop=True)
    fixtures_path = OUT_DIR / 'r1_fixtures_2023_2025.csv'
    fixtures_df.to_csv(fixtures_path, index=False, encoding='utf-8-sig')

    fixture_dates = sorted(fixtures_df['date'].dropna().astype(str).unique().tolist())
    fixture_id_set = set(fixtures_df['fixture_id'].astype(int).tolist())
    odds_summary_rows = []
    odds_raw_rows = []

    for idx, date_str in enumerate(fixture_dates, 1):
        page = 1
        total = 1
        kept = 0
        while page <= total:
            payload = fetch_json(session, base_url, '/odds', {'date': date_str, 'page': page})
            total = int((payload.get('paging') or {}).get('total', 1) or 1)
            response = payload.get('response', [])
            for item in response:
                fixture_id = int(item['fixture']['id'])
                if fixture_id not in fixture_id_set:
                    continue
                bookmakers = item.get('bookmakers', [])
                chosen_total = choose_main_total(bookmakers)
                chosen_1x2 = choose_main_1x2(bookmakers)
                odds_summary_rows.append({
                    'fixture_id': fixture_id,
                    'date': date_str,
                    'bookmakers_count': len(bookmakers),
                    **(chosen_total or {'ou_line': np.nan, 'over_odds': np.nan, 'under_odds': np.nan, 'ou_quote_count': 0, 'fair_over_open': np.nan}),
                    **chosen_1x2,
                })
                odds_raw_rows.append({
                    'fixture_id': fixture_id,
                    'date': date_str,
                    'raw_json': json.dumps(item, ensure_ascii=False),
                })
                kept += 1
            page += 1
        print(f'[odds_by_date] {idx}/{len(fixture_dates)} {date_str} kept={kept}')

    odds_df = pd.DataFrame(odds_summary_rows).drop_duplicates(subset=['fixture_id'], keep='last').sort_values(['date', 'fixture_id']).reset_index(drop=True)
    odds_df.to_csv(OUT_DIR / 'r1_odds_summary_2023_2025.csv', index=False, encoding='utf-8-sig')
    pd.DataFrame(odds_raw_rows).to_json(OUT_DIR / 'r1_odds_raw_2023_2025.jsonl', orient='records', lines=True, force_ascii=False)

    merged = fixtures_df.merge(odds_df, on=['fixture_id', 'date'], how='left')
    merged['total_goals'] = merged['home_goals'].fillna(0) + merged['away_goals'].fillna(0)
    merged['is_completed'] = merged['status'].isin(['FT', 'AET', 'PEN'])
    merged['has_ou_market'] = merged['ou_line'].notna()
    merged.to_csv(OUT_DIR / 'r1_goal_number_dataset_2023_2025.csv', index=False, encoding='utf-8-sig')

    summary = {
        'league_id': LEAGUE_ID,
        'seasons': SEASONS,
        'fixtures_rows': int(len(fixtures_df)),
        'distinct_dates': int(len(fixture_dates)),
        'odds_summary_rows': int(len(odds_df)),
        'merged_rows': int(len(merged)),
        'completed_rows': int(merged['is_completed'].sum()),
        'completed_with_ou_market': int((merged['is_completed'] & merged['has_ou_market']).sum()),
        'files': {
            'fixtures_csv': str(fixtures_path),
            'odds_summary_csv': str(OUT_DIR / 'r1_odds_summary_2023_2025.csv'),
            'odds_raw_jsonl': str(OUT_DIR / 'r1_odds_raw_2023_2025.jsonl'),
            'merged_dataset_csv': str(OUT_DIR / 'r1_goal_number_dataset_2023_2025.csv'),
        },
    }
    (OUT_DIR / 'summary.json').write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding='utf-8')
    print(json.dumps(summary, ensure_ascii=False, indent=2))


if __name__ == '__main__':
    collect()
