"""Fikstür senkronu ve canlı maç takibi.

- sync_fixtures(db): açık ligler için gelecek + son maçları Match tablosuna işler.
- poll_live(db):    canlı feed'i tarar, skor/durum günceller, yeni olayları
                    (gol, kart, penaltı, devre, maç sonu...) MatchEvent olarak üretir.

Ürettiği MatchEvent kayıtları Faz 4/5'te içerik motorunu besler.
"""
import logging
from datetime import datetime, timezone

from sqlalchemy.orm import Session

from app.db import models, crud
from app.data_engine import sofascore as ss

log = logging.getLogger("data.sync")


# ------------------------------------------------------------------ yardımcılar
def _utc(ts):
    if not ts:
        return None
    return datetime.fromtimestamp(ts, tz=timezone.utc).replace(tzinfo=None)


def _map_status(ev: dict) -> str:
    t = (ev.get("status") or {}).get("type", "")
    desc = (ev.get("status") or {}).get("description", "")
    if t == "inprogress":
        return "ht" if "Halftime" in desc else "live"
    if t == "finished":
        return "finished"
    if t in ("postponed", "canceled", "cancelled"):
        return "cancelled"
    return "scheduled"


def _upsert_match(db: Session, ev: dict, league: models.League) -> models.Match:
    pid = str(ev["id"])
    m = (
        db.query(models.Match)
        .filter_by(provider="sofascore", provider_match_id=pid)
        .first()
    )
    if not m:
        m = models.Match(provider="sofascore", provider_match_id=pid)
        db.add(m)
    hs, as_ = ss.event_scores(ev)
    m.league_id = league.id
    m.home_team = ev["homeTeam"]["name"]
    m.away_team = ev["awayTeam"]["name"]
    m.home_score = hs
    m.away_score = as_
    m.status = _map_status(ev)
    m.kickoff = _utc(ev.get("startTimestamp"))
    m.last_synced = datetime.utcnow()
    return m


def _add_event(db: Session, match: models.Match, event_type: str, dedup_key: str,
               minute="", team="", player="", detail="") -> models.MatchEvent | None:
    """Dedup anahtarı daha önce görülmediyse MatchEvent ekler ve döner."""
    exists = (
        db.query(models.MatchEvent.id)
        .filter_by(match_id=match.id, dedup_key=dedup_key)
        .first()
    )
    if exists:
        return None
    e = models.MatchEvent(
        match_id=match.id, event_type=event_type, dedup_key=dedup_key,
        minute=str(minute or ""), team=team or "", player=player or "",
        detail=detail or "",
    )
    db.add(e)
    # FLUSH ŞART: oturum autoflush=False. Aynı tarama turunda hem durum-geçişi
    # hem incident yolu 'ht' eklemeye çalıştığında, flush olmadan ikincisinin
    # exists-sorgusu birincisini GÖREMİYOR ve aynı dedup anahtarıyla çift
    # olay oluşuyordu (18.08 Deportivo maçında iki 'ht' aynı saniyede).
    db.flush()
    return e


# ------------------------------------------------------------------ fikstür
def _enabled_leagues(db: Session):
    return (
        db.query(models.League)
        .filter(models.League.enabled == True)  # noqa: E712
        .all()
    )


def sync_fixtures(db: Session) -> int:
    """Açık liglerin gelecek + son maçlarını çeker. Eklenen/güncellenen maç sayısı döner."""
    count = 0
    for league in _enabled_leagues(db):
        utid = ss.TOURNAMENT_IDS.get(league.key)
        if not utid:
            continue
        # sezon id'yi önbellekle (League.provider_id alanında tutulur)
        season_id = None
        if league.provider_id and ":" in league.provider_id:
            _, cached = league.provider_id.split(":", 1)
            season_id = int(cached) if cached.isdigit() else None
        if not season_id:
            season_id = ss.get_current_season_id(utid)
            if not season_id:
                log.warning("Sezon bulunamadı: %s", league.key)
                continue
            league.provider_id = f"{utid}:{season_id}"

        found = 0
        for which in ("next", "last"):
            for ev in ss.get_fixture_events(utid, season_id, which, 0):
                _upsert_match(db, ev, league)
                found += 1

        # Önbellekteki sezon bitmişse (yeni sezon başladı) hiç maç dönmez:
        # güncel sezonu yeniden çöz ve bir kez daha dene.
        if found == 0:
            fresh = ss.get_current_season_id(utid)
            if fresh and fresh != season_id:
                league.provider_id = f"{utid}:{fresh}"
                crud.add_log(db, f"{league.name}: yeni sezona geçildi ({fresh})",
                             source="data")
                for which in ("next", "last"):
                    for ev in ss.get_fixture_events(utid, fresh, which, 0):
                        _upsert_match(db, ev, league)
                        found += 1
        count += found
        db.commit()
    if count:
        crud.add_log(db, f"Fikstür senkronu: {count} maç işlendi", source="data")
    return count


# ------------------------------------------------------------------ canlı takip
# SofaScore incident tipi -> bizim olay tipimiz
_INCIDENT_MAP = {
    ("goal", None): "goal",
    ("card", "yellow"): "yellow_card",
    ("card", "yellowRed"): "second_yellow",
    ("card", "red"): "red_card",
    ("penalty", "missed"): "penalty_missed",
    ("varDecision", None): "var",
    ("period", None): "period",
}


def _classify_incident(inc: dict):
    itype = inc.get("incidentType")
    iclass = inc.get("incidentClass")
    if itype == "goal":
        if iclass == "penalty":
            return "penalty_goal"
        if iclass == "ownGoal":
            return "own_goal"
        return "goal"
    if itype == "inGamePenalty":
        # DİKKAT: inGamePenalty GOL DEĞİLDİR. Penaltının kendisiyle ilgili
        # bir olaydır: 'awarded' = penaltı verildi, 'missed'/'saved' = kaçtı.
        # Gerçek penaltı GOLLERİ kaynağa `goal` + class `penalty` olarak
        # geliyor (yukarıda ele alınıyor).
        # 17.08 Batman maçında 'awarded' gol sanılıp "Penaltıdan gole
        # kavuştu! 0-1" diye YANLIŞ tweet atıldı; skor 0-0'dı.
        if iclass in ("missed", "saved"):
            return "penalty_missed"
        return None      # 'awarded' vb. tek başına tweet'lik değil
    if itype == "substitution":
        # Sakatlık kaynaklı çıkışlar haber değeri taşır; rutin değişiklikler değil.
        return "injury" if iclass == "injury" else None
    return _INCIDENT_MAP.get((itype, iclass)) or _INCIDENT_MAP.get((itype, None))


def _goal_milestone(db: Session, match: models.Match, player: str) -> str | None:
    """Aynı oyuncunun bu maçtaki gol sayısına göre 'brace'/'hat_trick' döner."""
    if not player:
        return None
    goals = (
        db.query(models.MatchEvent)
        .filter(
            models.MatchEvent.match_id == match.id,
            models.MatchEvent.player == player,
            models.MatchEvent.event_type.in_(["goal", "penalty_goal"]),
        )
        .count()
    )
    if goals == 3:
        return "hat_trick"
    if goals == 2:
        return "brace"
    return None


def _team_name(match: models.Match, is_home):
    if is_home is True:
        return match.home_team
    if is_home is False:
        return match.away_team
    return ""


def _process_incidents(db: Session, match: models.Match) -> list[models.MatchEvent]:
    """Maçın incident listesini çeker, yeni olanları MatchEvent yapar."""
    new_events = []
    incidents = ss.get_incidents(int(match.provider_match_id))
    for inc in incidents:
        kind = _classify_incident(inc)
        if not kind:
            continue  # değişiklik (substitution) vb. tweet'e değmez
        # SofaScore bazen negatif/anlamsız dakika döndürüyor (canlı akışta
        # "-5" görüldü) ve LLM bunu "5. dakika" diye yazıyordu. Geçersizse
        # dakika hiç yazılmaz — yanlış bilgi vermektense boş bırak.
        minute = inc.get("time")
        try:
            minute = "" if minute is None or int(minute) < 0 else str(minute)
        except (TypeError, ValueError):
            minute = ""
        player = (inc.get("player") or {}).get("name", "")
        text = inc.get("text") or ""
        if kind == "period":
            # HT / FT / 2. yarı başlangıcı gibi periyot işaretleri
            if text not in ("HT", "FT", "AET", "PEN"):
                continue
            kind = {"HT": "ht", "FT": "ft", "AET": "ft", "PEN": "shootout"}[text]
            dedup = f"period:{text}"
            detail = f"{match.home_score}-{match.away_score}"
            e = _add_event(db, match, kind, dedup, minute=minute, detail=detail)
        elif kind in ("goal", "penalty_goal", "own_goal"):
            dedup = f"{kind}:{minute}:{player or inc.get('id', '')}"
            detail = f"{match.home_score}-{match.away_score}"
            e = _add_event(
                db, match, kind, dedup, minute=minute,
                team=_team_name(match, inc.get("isHome")), player=player, detail=detail,
            )
            if e:
                db.flush()
                # 90+ dakikada gelen gol ayrı bir anlatı hak eder
                try:
                    if kind == "goal" and int(str(minute).split("+")[0]) >= 88:
                        e.event_type = "goal_late"
                except (ValueError, TypeError):
                    pass
                # aynı oyuncunun 2./3. golü
                milestone = _goal_milestone(db, match, player)
                if milestone:
                    ms = _add_event(
                        db, match, milestone, f"{milestone}:{player}",
                        minute=minute, team=e.team, player=player, detail=detail,
                    )
                    if ms:
                        new_events.append(ms)
        else:
            # BİLGİSİZ OLAY ATLANIR: ne oyuncu ne dakika varsa söylenecek bir
            # şey yok. "Sarı kart çıktı" gibi boş tweetler değersiz.
            if not player and not minute and kind not in ("var",):
                continue

            # Tekrar anahtarı: kaynağın kendi olay kimliği varsa EN SAĞLAMI
            # odur (dakika düzeltmelerinden etkilenmez). Yoksa dakikaya
            # dönülür — çünkü dakika olmadan aynı oyuncunun ikinci golü
            # tekrar sanılıp YUTULUR. Ham dakika kullanılır: normalizasyon
            # değişse bile anahtar sabit kalsın.
            ham_dk = inc.get("time")
            olay_kimlik = inc.get("id") or f"{ham_dk}:{player or text}"
            dedup = f"{kind}:{olay_kimlik}"
            detail = f"{match.home_score}-{match.away_score}"
            if kind == "var":
                detail = text or (inc.get("incidentClass") or "")
            e = _add_event(
                db, match, kind, dedup, minute=minute,
                team=_team_name(match, inc.get("isHome")), player=player, detail=detail,
            )
        if e:
            new_events.append(e)
    return new_events


def poll_live(db: Session) -> list[models.MatchEvent]:
    """Ana canlı döngü adımı. Yeni MatchEvent listesi döner."""
    leagues = {ss.TOURNAMENT_IDS[l.key]: l for l in _enabled_leagues(db) if l.key in ss.TOURNAMENT_IDS}
    if not leagues:
        return []

    new_events: list[models.MatchEvent] = []
    live_ids_seen: set[str] = set()

    live = ss.get_live_events()
    for ev in live:
        utid = ss.event_unique_tournament_id(ev)
        if utid not in leagues:
            continue
        league = leagues[utid]
        was = (
            db.query(models.Match.status)
            .filter_by(provider="sofascore", provider_match_id=str(ev["id"]))
            .scalar()
        )
        m = _upsert_match(db, ev, league)
        db.flush()  # m.id lazım
        live_ids_seen.add(m.provider_match_id)

        # başlama düdüğü: scheduled -> live geçişi
        if was in (None, "scheduled") and m.status in ("live", "ht"):
            e = _add_event(db, m, "kickoff", "kickoff", detail="Maç başladı")
            if e:
                new_events.append(e)

        # DEVRE ARASI / İKİNCİ YARI: maçın DURUMUNDAN türetilir.
        # Incident metnine güvenilemiyor: kaynak bazen "HT" bazen
        # "First half" gönderiyor; etikete bakan kod devre arasını
        # tamamen kaçırabiliyordu. Durum geçişi ise kesin.
        if was == "live" and m.status == "ht":
            e = _add_event(db, m, "ht", "period:HT",
                           minute="45", detail=f"{m.home_score}-{m.away_score}")
            if e:
                new_events.append(e)
        elif was == "ht" and m.status == "live":
            e = _add_event(db, m, "second_half", "period:2H",
                           minute="46", detail=f"{m.home_score}-{m.away_score}")
            if e:
                new_events.append(e)

        new_events.extend(_process_incidents(db, m))

    # canlı listeden düşen maçlar: bitti mi kontrol et (FT olayını kaçırma)
    stale = (
        db.query(models.Match)
        .filter(models.Match.status.in_(["live", "ht"]))
        .all()
    )
    for m in stale:
        if m.provider_match_id in live_ids_seen:
            continue
        detail = ss.get_event(int(m.provider_match_id))
        if not detail:
            continue
        hs, as_ = ss.event_scores(detail)
        m.home_score, m.away_score = hs, as_
        m.status = _map_status(detail)
        m.last_synced = datetime.utcnow()
        if m.status == "finished":
            e = _add_event(db, m, "ft", "period:FT", detail=f"{hs}-{as_}")
            if e:
                new_events.append(e)

    db.commit()
    for e in new_events:
        db.refresh(e)
    if new_events:
        crud.add_log(
            db, f"Canlı tarama: {len(new_events)} yeni olay", source="data",
        )
    return new_events


# ------------------------------------------------------------------ maç öncesi
def poll_previews(db: Session, hours_ahead: int = 3) -> list[models.MatchEvent]:
    """Başlamasına ~3 saat kalan maçlar için tanıtım olayı üretir (maç başına bir kez)."""
    from datetime import timedelta
    now = datetime.utcnow()
    soon = (
        db.query(models.Match)
        .filter(
            models.Match.status == "scheduled",
            models.Match.kickoff != None,  # noqa: E711
            models.Match.kickoff > now,
            models.Match.kickoff < now + timedelta(hours=hours_ahead),
        )
        .order_by(models.Match.kickoff)
        .all()
    )
    new_events = []
    for m in soon:
        league = m.league.name if m.league else ""
        kickoff_local = m.kickoff + timedelta(hours=3)  # UTC -> TR saati
        detail = f"{league} · {kickoff_local.strftime('%H:%M')}"
        e = _add_event(db, m, "preview", "preview", detail=detail)
        if e:
            new_events.append(e)
    db.commit()
    for e in new_events:
        db.refresh(e)
    return new_events


# ------------------------------------------------------------------ ilk 11'ler
def poll_lineups(db: Session) -> list[models.MatchEvent]:
    """Başlamasına ~70 dk kalan maçların ilk 11'ini bir kez yakalar."""
    from datetime import timedelta
    now = datetime.utcnow()
    soon = (
        db.query(models.Match)
        .filter(
            models.Match.status == "scheduled",
            models.Match.kickoff != None,  # noqa: E711
            models.Match.kickoff > now,
            models.Match.kickoff < now + timedelta(minutes=70),
        )
        .all()
    )
    new_events = []
    for m in soon:
        already = (
            db.query(models.MatchEvent.id)
            .filter_by(match_id=m.id, dedup_key="lineup")
            .first()
        )
        if already:
            continue
        data = ss.get_lineups(int(m.provider_match_id))
        if not data or not data.get("confirmed"):
            continue

        def xi(side):
            players = [
                p["player"]["name"]
                for p in (data.get(side, {}).get("players") or [])
                if not p.get("substitute")
            ]
            form = data.get(side, {}).get("formation", "")
            return form, players

        hf, hp = xi("home")
        af, ap = xi("away")
        if not hp or not ap:
            continue
        detail = (
            f"{m.home_team} ({hf}): {', '.join(hp)}\n"
            f"{m.away_team} ({af}): {', '.join(ap)}"
        )
        e = _add_event(db, m, "lineup", "lineup", detail=detail)
        if e:
            new_events.append(e)
    db.commit()
    for e in new_events:
        db.refresh(e)
    return new_events
