"""Günlük bütçe planlayıcı — tavanı değil, kapsamı uyarlar.

NEDEN TAVAN OTOMATİK ARTMIYOR?
------------------------------
Günlük tavan bir güvenlik freni: hesabın ne kadar görünür olacağını belirler.
Otomatik artan bir tavan, en çok maçın olduğu günde (yani hesabın zaten en çok
tweet attığı, en dikkat çektiği günde) kendini en yükseğe çıkarır — frenin
amacının tam tersi. Bu yüzden tavan kullanıcının elinde kalır.

ESNEYEN ŞEY KAPSAM
------------------
Sistem günün fikstürüne bakar, o gün kaç tweet gerekeceğini tahmin eder ve
kalan bütçeye sığmıyorsa DÜŞÜK ÖNCELİKLİ liglerin anlatımını kademe kademe
hafifletir. Böylece bütçe biterken Süper Lig derbisi yarıda kalmaz; onun
yerine Ligue 1 maçı "sadece sonuç"a düşer.

Kullanıcının elle seçtiği kademe TAVANDIR: planlayıcı yalnızca aşağı çeker,
asla kullanıcının istediğinden fazla tweet attırmaz. Yalnız 'auto' bırakılan
ligler bütçe bolsa yukarı da çıkabilir.

Tahmin ağırlıkları gerçek maç verisinden değil kabaca ortalamadan gelir;
amaç kesin sayı değil, doğru sıralamada fedakârlık yapmaktır.
"""
import json
import logging
from datetime import datetime, timedelta

from sqlalchemy.orm import Session

from app.db import models, crud
from app.core import timeutil
from app.scheduler import coverage

log = logging.getLogger("scheduler.budget")

# Bir maçın kademesine göre beklenen tweet sayısı (kabaca ortalama)
TIER_COST = {
    "full": 10,        # tanıtım, 11'ler, başlama, goller, kartlar, devre, son
    "standard": 6,     # başlama, goller, kırmızı, devre, son
    "minimal": 4,      # goller, kırmızı, son
    "result_only": 1,  # yalnız skor
}

# Hafiften ağıra sıra (düşürürken bu sıranın tersine inilir)
TIER_ORDER = ["result_only", "minimal", "standard", "full"]

# Maç dışı içerik için ayrılan pay (haber + günlük fikstür/sonuç/puan durumu)
NON_MATCH_RESERVE = 40

PLAN_KEY = "coverage_plan"        # {lig_key: kademe} JSON
PLAN_INFO_KEY = "coverage_plan_info"   # panelde göstermek için özet


def _todays_matches(db: Session):
    """Türkiye gününün kalanında oynanacak/oynanan maçlar.

    "Bugün" UTC gününe göre hesaplanırsa gece 21:00'den sonra Türkiye'deki
    akşam maçları kapsam dışı kalıyordu (bkz. core/timeutil).
    """
    now = datetime.utcnow()
    _, day_end = timeutil.tr_day_bounds_utc(now)
    return (
        db.query(models.Match)
        .join(models.League, models.Match.league_id == models.League.id)
        .filter(
            models.League.enabled == True,  # noqa: E712
            models.Match.kickoff != None,   # noqa: E711
            models.Match.kickoff >= now - timedelta(hours=3),
            models.Match.kickoff <= day_end,
            models.Match.status.in_(["scheduled", "live", "ht"]),
        )
        .all()
    )


def remaining_budget(db: Session) -> int:
    """Bugün için kalan tweet hakkı (tüm aktif hesapların toplamı)."""
    cap = int(crud.get_setting(db, "daily_post_cap", "120") or 120)
    accounts = (
        db.query(models.Account)
        .filter(models.Account.is_active == True,  # noqa: E712
                models.Account.status.in_(["ok", "unknown"]))
        .count()
    ) or 1
    total = cap * accounts
    start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
    used = (
        db.query(models.Post)
        .filter(models.Post.status == "posted", models.Post.posted_at >= start)
        .count()
    )
    return max(0, total - used), total, used


def plan(db: Session) -> dict:
    """Bugünün kapsam planını hesaplar ve kaydeder. Panelde de gösterilir."""
    remaining, total, used = remaining_budget(db)
    matches = _todays_matches(db)

    # lig -> (lig nesnesi, bugünkü maç sayısı)
    per_league: dict[int, list] = {}
    for m in matches:
        per_league.setdefault(m.league_id, [m.league, 0])
        per_league[m.league_id][1] += 1

    # kullanıcının seçtiği kademe = tavan
    ceiling = {lid: coverage.tier_for_league(lg) for lid, (lg, _) in per_league.items()}
    chosen = dict(ceiling)

    def projected() -> int:
        return sum(TIER_COST[chosen[lid]] * cnt
                   for lid, (_, cnt) in per_league.items())

    available = max(0, remaining - NON_MATCH_RESERVE)
    demoted = []

    # Bütçeye sığana kadar hafiflet. Her turda YALNIZCA BİR ADIM ve
    # "en az önemli + şu an en ağır" ligden başlayarak.
    #
    # Naif yaklaşım (bir ligi dibe vurana kadar düşürüp sonrakine geçmek)
    # aşırı kesiyordu: tek hamlede 8 ligi birden 'sadece sonuç'a indiriyordu.
    # Adım adım ve sırayla inince fedakârlık dengeli dağılıyor ve bütçe
    # yeterse ligler orta kademelerde kalabiliyor.
    guard = 0
    while projected() > available and guard < 400:
        guard += 1
        adaylar = [
            (lid, lg) for lid, (lg, _) in per_league.items()
            if TIER_ORDER.index(chosen[lid]) > 0
        ]
        if not adaylar:
            break   # hepsi en alt kademede, yapacak bir şey yok
        # önce en düşük öncelikli (priority büyük), eşitse en ağır kademeli
        lid, lg = max(
            adaylar,
            key=lambda x: ((per_league[x[0]][0].priority or 5),
                           TIER_ORDER.index(chosen[x[0]]),
                           per_league[x[0]][1]),
        )
        cur = chosen[lid]
        chosen[lid] = TIER_ORDER[TIER_ORDER.index(cur) - 1]
        demoted.append(f"{lg.name}: {cur} → {chosen[lid]}")

    plan_map = {}
    for lid, (lg, _) in per_league.items():
        plan_map[lg.key] = chosen[lid]

    info = {
        "matches_today": len(matches),
        "remaining": remaining,
        "total": total,
        "used": used,
        "reserve": NON_MATCH_RESERVE,
        "projected": projected(),
        "demoted": demoted[-12:],
        "computed_at": datetime.utcnow().isoformat(timespec="seconds"),
    }
    crud.set_setting(db, PLAN_KEY, json.dumps(plan_map))
    crud.set_setting(db, PLAN_INFO_KEY, json.dumps(info))
    db.commit()

    if demoted:
        crud.add_log(
            db,
            f"Bütçe planı: {len(matches)} maç, {info['projected']} tweet öngörüsü, "
            f"{len(demoted)} lig hafifletildi",
            source="scheduler",
        )
    return info


def get_plan(db: Session) -> dict:
    raw = crud.get_setting(db, PLAN_KEY, "")
    if not raw:
        return {}
    try:
        return json.loads(raw)
    except ValueError:
        return {}


def get_plan_info(db: Session) -> dict:
    raw = crud.get_setting(db, PLAN_INFO_KEY, "")
    if not raw:
        return {}
    try:
        return json.loads(raw)
    except ValueError:
        return {}
