import time as _time
import sqlite3
import logging
from datetime import datetime, timedelta
from pathlib import Path
from zoneinfo import ZoneInfo

import aiohttp

import config
from games.casino import get_user_length, update_user_length

logger = logging.getLogger(__name__)

_matches_cache: dict[str, tuple[float, list]] = {}
_sports_cache: tuple[float, list] | None = None
_last_viewed_sport_by_user: dict[int, str] = {}
_DRAW_NAMES = {"draw", "tie", "ничья"}

# Сколько дней хранить историю ставок
BET_HISTORY_DAYS = 30


def _init_bets_db() -> None:
    Path(config.BET_DB_PATH).parent.mkdir(parents=True, exist_ok=True)
    with sqlite3.connect(config.BET_DB_PATH) as conn:
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS bets (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                match_id TEXT NOT NULL,
                sport TEXT NOT NULL,
                home_team TEXT NOT NULL,
                away_team TEXT NOT NULL,
                chosen_team TEXT NOT NULL,
                amount REAL NOT NULL,
                odds REAL NOT NULL,
                status TEXT DEFAULT 'pending',
                payout REAL DEFAULT 0,
                created_ts INTEGER,
                resolved_ts INTEGER
            )
            """
        )
        conn.execute("CREATE INDEX IF NOT EXISTS idx_bets_user ON bets(user_id, status)")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_bets_match ON bets(match_id, status)")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_bets_created ON bets(created_ts)")

        # Миграция: добавить столбцы если их нет
        cursor = conn.execute("PRAGMA table_info(bets)")
        columns = {row[1] for row in cursor.fetchall()}

        if "payout" not in columns:
            conn.execute("ALTER TABLE bets ADD COLUMN payout REAL DEFAULT 0")
        if "resolved_ts" not in columns:
            conn.execute("ALTER TABLE bets ADD COLUMN resolved_ts INTEGER")

        conn.commit()


_init_bets_db()


def remember_user_sport_context(user_id: int, sport_alias: str) -> None:
    if sport_alias in config.ODDS_SPORTS:
        _last_viewed_sport_by_user[user_id] = sport_alias


def clear_user_sport_context(user_id: int) -> None:
    _last_viewed_sport_by_user.pop(user_id, None)


def get_user_sport_context(user_id: int) -> str | None:
    return _last_viewed_sport_by_user.get(user_id)


async def _fetch_available_sports() -> list[dict]:
    """Получить список доступных видов спорта из API."""
    global _sports_cache
    now = _time.time()
    if _sports_cache and (now - _sports_cache[0]) < 3600:
        return _sports_cache[1]

    if not config.ODDS_API_KEY:
        return []

    url = f"{config.ODDS_API_BASE}/sports"
    params = {"apiKey": config.ODDS_API_KEY}

    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(url, params=params, timeout=aiohttp.ClientTimeout(total=15)) as resp:
                if resp.status != 200:
                    body = await resp.text()
                    logger.warning(f"Sports API returned {resp.status}: {body[:200]}")
                    return []
                data = await resp.json()
                _sports_cache = (now, data)
                return data
    except Exception:
        logger.exception("Failed to fetch sports list")
        return []


async def fetch_matches(sport_key: str) -> list[dict]:
    now = _time.time()
    cached = _matches_cache.get(sport_key)
    if cached and (now - cached[0]) < config.ODDS_CACHE_TTL:
        return cached[1]

    if not config.ODDS_API_KEY:
        logger.warning("ODDS_API_KEY не задан, матчи недоступны")
        return []

    url = f"{config.ODDS_API_BASE}/sports/{sport_key}/odds"
    params = {
        "apiKey": config.ODDS_API_KEY,
        "regions": "eu",
        "markets": "h2h",
        "oddsFormat": "decimal",
    }

    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(url, params=params, timeout=aiohttp.ClientTimeout(total=15)) as resp:
                if resp.status != 200:
                    body = await resp.text()
                    logger.warning(f"Odds API returned {resp.status} for {sport_key}: {body[:300]}")
                    return cached[1] if cached else []
                data = await resp.json()
                logger.info(f"Odds API for {sport_key}: received {len(data)} events")
    except Exception:
        logger.exception(f"Failed to fetch odds for {sport_key}")
        return cached[1] if cached else []

    if not isinstance(data, list):
        logger.warning(f"Odds API returned non-list for {sport_key}: {type(data)}")
        return cached[1] if cached else []

    matches = []
    for event in data:
        best_odds = {}
        for bookmaker in event.get("bookmakers", []):
            for market in bookmaker.get("markets", []):
                if market.get("key") == "h2h":
                    for outcome in market.get("outcomes", []):
                        name = outcome.get("name", "")
                        price = outcome.get("price", 0)
                        if name and price and (name not in best_odds or price > best_odds[name]):
                            best_odds[name] = price

        if len(best_odds) >= 2:
            matches.append({
                "id": event.get("id", ""),
                "home": event.get("home_team", ""),
                "away": event.get("away_team", ""),
                "commence": event.get("commence_time", ""),
                "odds": best_odds,
            })

    _matches_cache[sport_key] = (now, matches)
    logger.info(f"Parsed {len(matches)} matches for {sport_key}")
    return matches


def _get_match_outcomes(match: dict) -> list[dict[str, str | float]]:
    odds = match.get("odds", {})
    outcomes: list[dict[str, str | float]] = []
    added: set[str] = set()

    def append_outcome(code: str, name: str) -> None:
        if name in odds and name not in added:
            outcomes.append({"code": code, "name": name, "odds": odds[name]})
            added.add(name)

    home = match.get("home", "")
    away = match.get("away", "")
    append_outcome("1", home)

    draw_name = next((name for name in odds if name.casefold() in _DRAW_NAMES), None)
    if draw_name:
        append_outcome("X", draw_name)

    append_outcome("2", away)

    next_code = 3
    for name in odds:
        if name in added:
            continue
        append_outcome(str(next_code), name)
        next_code += 1

    return outcomes


def describe_match_outcomes(match: dict) -> str:
    return ", ".join(
        f"{item['code']}={item['name']} x{item['odds']:.2f}"
        for item in _get_match_outcomes(match)
    )


def resolve_match_outcome(match: dict, raw_choice: str) -> str | None:
    choice = raw_choice.strip()
    if not choice:
        return None

    normalized = choice.casefold()
    outcomes = _get_match_outcomes(match)

    for item in outcomes:
        if normalized == str(item["code"]).casefold():
            return str(item["name"])

    if normalized in _DRAW_NAMES or normalized == "x":
        for item in outcomes:
            if str(item["code"]).casefold() == "x":
                return str(item["name"])

    for item in outcomes:
        if normalized == str(item["name"]).casefold():
            return str(item["name"])

    try:
        choice_odds = round(float(choice.replace(",", ".")), 2)
    except ValueError:
        return None

    for item in outcomes:
        if round(float(item["odds"]), 2) == choice_odds:
            return str(item["name"])

    return None


def format_matches(matches: list[dict], sport_alias: str, sport_label: str) -> str:
    if not matches:
        return f"🏟 {sport_label} [{sport_alias}] — матчей не найдено."

    lines = [f"🏟 {sport_label} [{sport_alias}] — ближайшие матчи:\n"]
    for i, m in enumerate(matches[:10], 1):
        outcomes = _get_match_outcomes(m)
        odds_str = "\n".join(
            f"   {item['code']}) {item['name']}: {item['odds']:.2f}"
            for item in outcomes
        )

        start_str = ""
        if m["commence"]:
            try:
                dt = datetime.fromisoformat(m["commence"].replace("Z", "+00:00"))
                msk = dt.astimezone(ZoneInfo(config.DEFAULT_TZ))
                start_str = f"\n   🕐 {msk.strftime('%d.%m %H:%M')} МСК"
            except Exception:
                pass

        lines.append(f"{i}️⃣ {m['home']} vs {m['away']}{start_str}\n{odds_str}\n")

    lines.append(f"Ставка: /bet {sport_alias} [номер] [исход] [ставка]")
    lines.append(f"Коротко после /matches {sport_alias}: /bet [номер] [исход] [ставка]")
    lines.append("Исход: 1/2, для ничьи X. Можно писать ещё название команды или коэффициент.")
    return "\n".join(lines)


def _get_sport_label(key: str) -> str:
    labels = {
        "epl": "⚽ EPL",
        "ucl": "⚽ Champions League",
        "laliga": "⚽ La Liga",
        "bundesliga": "⚽ Bundesliga",
        "seriea": "⚽ Serie A",
        "ligue1": "⚽ Ligue 1",
        "europa": "⚽ Europa League",
    }
    return labels.get(key, key)


async def get_formatted_matches(sport_alias: str | None) -> str:
    if not config.ODDS_API_KEY:
        return "⚠️ ODDS_API_KEY не задан. Ставки недоступны."

    if sport_alias and sport_alias in config.ODDS_SPORTS:
        sport_key = config.ODDS_SPORTS[sport_alias]
        matches = await fetch_matches(sport_key)
        if not matches:
            # Попробуем проверить, существует ли вообще этот спорт
            sports = await _fetch_available_sports()
            valid_keys = {s.get("key") for s in sports}
            if sport_key not in valid_keys:
                # Ищем похожий ключ
                suggestions = [s for s in sports if sport_alias in s.get("key", "").lower()
                               or sport_alias in s.get("title", "").lower()]
                if suggestions:
                    suggest_text = "\n".join(
                        f"  • {s['key']} — {s.get('title', '')}"
                        for s in suggestions[:5]
                    )
                    return (
                        f"⚠️ Ключ {sport_key} не найден в API.\n"
                        f"Похожие виды спорта:\n{suggest_text}\n\n"
                        f"Обнови sport keys в config.py"
                    )
                return (
                    f"⚠️ Ключ {sport_key} не найден в API. "
                    f"Проверь ODDS_SPORTS в config.py"
                )
        return format_matches(matches, sport_alias, _get_sport_label(sport_alias))

    parts = []
    for alias, sport_key in config.ODDS_SPORTS.items():
        matches = await fetch_matches(sport_key)
        parts.append(format_matches(matches, alias, _get_sport_label(alias)))

    result = "\n\n".join(parts)
    if all("матчей не найдено" in p for p in parts):
        result += "\n\n💡 Если матчей нет — возможно, sport keys устарели. Проверь /sports_debug"
    return result


async def get_match_by_index(sport_alias: str, index: int) -> dict | None:
    sport_key = config.ODDS_SPORTS.get(sport_alias)
    if not sport_key:
        return None
    matches = await fetch_matches(sport_key)
    if 0 < index <= len(matches):
        return matches[index - 1]
    return None


def place_bet(user_id: int, match: dict, sport: str, chosen_team: str, amount: float) -> str:
    current = get_user_length(user_id)
    if current is None:
        return "Сначала заведи счёт через /penis, братуха."

    if current <= 0:
        return "🚫 С кредитом ставки не принимаем."

    if amount <= 0:
        return "Ставка должна быть больше нуля."

    if amount > config.BET_MAX_AMOUNT:
        return f"Максимальная ставка — {config.BET_MAX_AMOUNT} см."

    if amount > current:
        return f"У тебя {current:.1f} см, а ставишь {amount:.1f}. Не хватает, фраер."

    team_odds = match["odds"].get(chosen_team)
    if team_odds is None:
        return f"Исход не найден. Доступные: {describe_match_outcomes(match)}"

    new_length = update_user_length(user_id, -amount)
    if new_length is None:
        return "Ошибка БД."

    now_ts = int(_time.time())
    try:
        with sqlite3.connect(config.BET_DB_PATH) as conn:
            conn.execute(
                """INSERT INTO bets (user_id, match_id, sport, home_team, away_team,
                   chosen_team, amount, odds, status, payout, created_ts)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?)""",
                (user_id, match["id"], sport, match["home"], match["away"],
                 chosen_team, amount, team_odds, now_ts),
            )
            conn.commit()
    except sqlite3.Error:
        logger.exception("Failed to place bet")
        update_user_length(user_id, amount)
        return "Ошибка БД."

    potential = round(amount * team_odds, 1)
    return (
        f"✅ Ставка принята!\n"
        f"🏟 {match['home']} vs {match['away']}\n"
        f"📌 {chosen_team} (x{team_odds:.2f})\n"
        f"💰 Ставка: {amount:.1f} см\n"
        f"🎯 Возможный выигрыш: {potential:.1f} см\n"
        f"📏 Остаток: {new_length:.1f} см"
    )


def get_user_bets(user_id: int) -> str:
    try:
        with sqlite3.connect(config.BET_DB_PATH) as conn:
            conn.row_factory = sqlite3.Row
            rows = conn.execute(
                "SELECT * FROM bets WHERE user_id = ? AND status = 'pending' ORDER BY created_ts DESC LIMIT 10",
                (user_id,),
            ).fetchall()
    except sqlite3.Error:
        return "Ошибка БД."

    if not rows:
        return "У тебя нет активных ставок."

    lines = ["📋 Твои активные ставки:\n"]
    for r in rows:
        potential = round(r["amount"] * r["odds"], 1)
        created = ""
        if r["created_ts"]:
            dt = datetime.fromtimestamp(r["created_ts"], tz=ZoneInfo(config.DEFAULT_TZ))
            created = f"  📅 {dt.strftime('%d.%m %H:%M')}\n"
        lines.append(
            f"• {r['home_team']} vs {r['away_team']}\n"
            f"  {r['chosen_team']} (x{r['odds']:.2f}), {r['amount']:.1f} см → {potential:.1f} см\n"
            f"{created}"
        )
    return "\n".join(lines)


def get_user_bet_history(user_id: int) -> str:
    """Полная история ставок за последний месяц."""
    cutoff_ts = int((_time.time()) - BET_HISTORY_DAYS * 86400)

    try:
        with sqlite3.connect(config.BET_DB_PATH) as conn:
            conn.row_factory = sqlite3.Row
            rows = conn.execute(
                """SELECT * FROM bets
                   WHERE user_id = ? AND created_ts >= ?
                   ORDER BY created_ts DESC
                   LIMIT 50""",
                (user_id, cutoff_ts),
            ).fetchall()
    except sqlite3.Error:
        logger.exception("Failed to get bet history")
        return "Ошибка БД."

    if not rows:
        return "📋 За последний месяц ставок не было."

    # Считаем статистику
    total_bet = 0.0
    total_won = 0.0
    total_lost = 0.0
    wins = 0
    losses = 0
    pending = 0

    lines = [f"📋 История ставок за {BET_HISTORY_DAYS} дней:\n"]

    for r in rows:
        status = r["status"]
        amount = float(r["amount"])
        odds_val = float(r["odds"])
        payout_raw = float(r["payout"] or 0)

        total_bet += amount

        if status == "won":
            wins += 1
            # Для старых ставок payout может быть 0, пересчитываем
            actual_payout = payout_raw if payout_raw > 0 else round(amount * odds_val, 1)
            total_won += actual_payout
            status_icon = "✅"
            result_text = f"+{actual_payout:.1f} см"
        elif status == "lost":
            losses += 1
            total_lost += amount
            status_icon = "❌"
            result_text = f"-{amount:.1f} см"
        else:
            pending += 1
            status_icon = "⏳"
            potential = round(amount * odds_val, 1)
            result_text = f"ожидание → {potential:.1f} см"

        created = ""
        if r["created_ts"]:
            dt = datetime.fromtimestamp(r["created_ts"], tz=ZoneInfo(config.DEFAULT_TZ))
            created = dt.strftime('%d.%m %H:%M')

        lines.append(
            f"{status_icon} {r['home_team']} vs {r['away_team']}\n"
            f"   {r['chosen_team']} (x{odds_val:.2f}) | {amount:.1f} см | {result_text}\n"
            f"   📅 {created}"
        )

    # Итоговая статистика
    net = total_won - total_lost
    net_sign = "+" if net >= 0 else ""

    lines.append("")
    lines.append("━━━ 📊 СТАТИСТИКА ━━━")
    lines.append(f"🎰 Всего ставок: {len(rows)}")
    lines.append(f"✅ Побед: {wins}")
    lines.append(f"❌ Поражений: {losses}")
    if pending:
        lines.append(f"⏳ В ожидании: {pending}")
    lines.append(f"💰 Поставлено: {total_bet:.1f} см")
    lines.append(f"🏆 Выиграно: +{total_won:.1f} см")
    lines.append(f"💸 Проиграно: -{total_lost:.1f} см")
    lines.append(f"📈 Итого: {net_sign}{net:.1f} см")

    if wins + losses > 0:
        winrate = wins / (wins + losses) * 100
        lines.append(f"📊 Винрейт: {winrate:.0f}%")

    return "\n".join(lines)


def cleanup_old_bets() -> int:
    """Удалить завершённые ставки старше BET_HISTORY_DAYS дней."""
    cutoff_ts = int(_time.time() - BET_HISTORY_DAYS * 86400)
    try:
        with sqlite3.connect(config.BET_DB_PATH) as conn:
            cursor = conn.execute(
                "DELETE FROM bets WHERE status != 'pending' AND created_ts < ?",
                (cutoff_ts,),
            )
            conn.commit()
            deleted = cursor.rowcount
            if deleted > 0:
                logger.info(f"Cleaned up {deleted} old bets")
            return deleted
    except sqlite3.Error:
        logger.exception("Failed to cleanup old bets")
        return 0


async def settle_bets() -> list[str]:
    notifications = []
    try:
        with sqlite3.connect(config.BET_DB_PATH) as conn:
            conn.row_factory = sqlite3.Row
            pending = conn.execute(
                "SELECT DISTINCT match_id, sport FROM bets WHERE status = 'pending'"
            ).fetchall()
    except sqlite3.Error:
        return notifications

    if not pending:
        return notifications

    now_ts = int(_time.time())

    for row in pending:
        match_id = row["match_id"]
        sport = row["sport"]
        sport_key = config.ODDS_SPORTS.get(sport, sport)

        try:
            url = f"{config.ODDS_API_BASE}/sports/{sport_key}/scores"
            params = {"apiKey": config.ODDS_API_KEY, "daysFrom": 3}
            async with aiohttp.ClientSession() as session:
                async with session.get(url, params=params, timeout=aiohttp.ClientTimeout(total=15)) as resp:
                    if resp.status != 200:
                        logger.warning(f"Scores API returned {resp.status} for {sport_key}")
                        continue
                    scores_data = await resp.json()
        except Exception:
            logger.exception(f"Failed to fetch scores for {sport_key}")
            continue

        for event in scores_data:
            if event.get("id") != match_id:
                continue
            if not event.get("completed"):
                continue

            scores = event.get("scores")
            if not scores:
                continue

            winner = None
            max_score = -1
            is_draw = False

            score_values = []
            for s in scores:
                score_val = int(s.get("score", 0))
                score_values.append((s.get("name", ""), score_val))

            if len(score_values) >= 2 and score_values[0][1] == score_values[1][1]:
                is_draw = True
                # Для ничьи ищем "Draw" среди ставок
                winner = "Draw"
            else:
                for name, score_val in score_values:
                    if score_val > max_score:
                        max_score = score_val
                        winner = name

            if not winner:
                continue

            try:
                with sqlite3.connect(config.BET_DB_PATH) as conn:
                    conn.row_factory = sqlite3.Row
                    bets = conn.execute(
                        "SELECT * FROM bets WHERE match_id = ? AND status = 'pending'",
                        (match_id,),
                    ).fetchall()

                    for bet in bets:
                        chosen = bet["chosen_team"]
                        bet_won = False

                        if is_draw:
                            # Ставка на ничью выигрывает
                            bet_won = chosen.casefold() in _DRAW_NAMES or chosen == "Draw"
                        else:
                            bet_won = chosen == winner

                        if bet_won:
                            winnings = round(bet["amount"] * bet["odds"], 1)
                            update_user_length(bet["user_id"], winnings)
                            conn.execute(
                                "UPDATE bets SET status = 'won', payout = ?, resolved_ts = ? WHERE id = ?",
                                (winnings, now_ts, bet["id"]),
                            )
                            notifications.append(
                                f"🎉 user_id={bet['user_id']}: выиграл {winnings:.1f} см "
                                f"({bet['home_team']} vs {bet['away_team']}, {bet['chosen_team']})"
                            )
                        else:
                            conn.execute(
                                "UPDATE bets SET status = 'lost', payout = 0, resolved_ts = ? WHERE id = ?",
                                (now_ts, bet["id"]),
                            )
                            notifications.append(
                                f"❌ user_id={bet['user_id']}: проиграл {bet['amount']:.1f} см "
                                f"({bet['home_team']} vs {bet['away_team']}, {bet['chosen_team']})"
                            )
                    conn.commit()
            except sqlite3.Error:
                logger.exception("Failed to settle bets")

    # Очистка старых ставок
    cleanup_old_bets()

    return notifications


async def debug_sports() -> str:
    """Диагностика: показать доступные виды спорта из API."""
    if not config.ODDS_API_KEY:
        return "⚠️ ODDS_API_KEY не задан."

    sports = await _fetch_available_sports()
    if not sports:
        return "❌ Не удалось получить список спортов от API."

    # Показываем текущие ключи и проверяем их
    current_keys = set(config.ODDS_SPORTS.values())
    valid_keys = {s.get("key") for s in sports}

    lines = ["🔍 Диагностика sport keys:\n"]

    for alias, key in config.ODDS_SPORTS.items():
        status = "✅" if key in valid_keys else "❌ НЕ НАЙДЕН"
        lines.append(f"  {alias} → {key} {status}")

    # Показать доступные esports и football
    lines.append("\n📋 Доступные eSports и футбол:")
    for s in sports:
        key = s.get("key", "")
        title = s.get("title", "")
        active = s.get("active", False)
        if active and ("esport" in key.lower() or "soccer" in key.lower() or "football" in key.lower()):
            marker = " ← ИСПОЛЬЗУЕТСЯ" if key in current_keys else ""
            lines.append(f"  • {key} — {title}{marker}")

    return "\n".join(lines)
