"""Dağıtıcı — kuyruktaki tweetleri insansı ritimle X'e gönderir.

Kurallar:
- Ana paylaşım anahtarı (posting_enabled) açık olmalı.
- Aktif saatler içinde olmalı (gece uykusu).
- İki tweet arası min–maks aralığında rastgele bekleme (bot gibi görünmemek için).
- Otomasyon modu:
    auto     -> 'queued' postlar doğrudan gönderilir
    approval -> yalnızca panelden 'approved' yapılan postlar gönderilir
- Rotasyon: günlük tavanı dolmamış, en az yük binen hesap seçilir.
- Bayat olaylar (gecikmiş gol vb.) gönderilmeden iptal edilir.
"""
import random
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.x_engine import client as xclient
from app.scheduler.enqueue import STALE_MINUTES, PRIORITY

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


def _get_int(db, key, default):
    try:
        return int(crud.get_setting(db, key, str(default)))
    except (ValueError, TypeError):
        return default


def _within_active_hours(db: Session) -> bool:
    """Aktif saat penceresi TÜRKİYE saatine göre değerlendirilir.

    Eskiden `datetime.now().hour` (SUNUCUNUN yerel saati) kullanılıyordu.
    Bu sunucu UTC-7'de, yani Türkiye'den 10 saat geride: 8-24 ayarı fiilen
    "TR 18:00 - 09:59 arası yayın, TR 10:00 - 17:59 arası sessizlik" anlamına
    geliyordu. Yani bot Türk günü boyunca susup bütün Türk gecesi tweet
    atıyordu — özelliğin tam tersi. 17.08'de 13:32-18:00 arası 4.5 saatlik
    yayın boşluğu ve bu sırada bayatlayıp iptal olan alıntılar bu yüzdendi.
    """
    start = _get_int(db, "active_hours_start", 8)
    end = _get_int(db, "active_hours_end", 24)
    hour = timeutil.tr_now().hour
    if end >= 24:
        return hour >= start
    if start <= end:
        return start <= hour < end
    # gece aşırı aralık (ör. 22 -> 2)
    return hour >= start or hour < end


def _last_post_at(db: Session) -> datetime | None:
    raw = crud.get_setting(db, "last_post_at", "")
    if not raw:
        return None
    try:
        return datetime.fromisoformat(raw)
    except ValueError:
        return None


def _required_gap(db: Session, is_reply: bool) -> int:
    """Gönderilecek tweetin şeridine göre gereken bekleme (sn).

    ÖNEMLİ: Bekleme, GÖNDERİLECEK tweete göre hesaplanır; önceki tweete
    göre DEĞİL. Eskiden tek bir `next_allowed_at` vardı ve ana akışa giden
    bir tweet (90-400 sn) thread şeridini de kilitliyordu: 17.08 Samsunspor
    maçında 3. dakika golü, başka maçın başlama tweeti yüzünden dakikalarca
    bekledi. Goller 12 dakikada bayatladığı için bu kayıp riskiydi.
    """
    if is_reply:
        lo = _get_int(db, "reply_min_gap_seconds", 15)
        hi = _get_int(db, "reply_max_gap_seconds", 45)
    else:
        lo = _get_int(db, "min_gap_seconds", 90)
        hi = _get_int(db, "max_gap_seconds", 400)
    if hi < lo:
        hi = lo
    return random.randint(lo, hi)


def _mark_sent(db: Session):
    """Son gönderim anını kaydet (bekleme buradan hesaplanır)."""
    crud.set_setting(db, "last_post_at", datetime.utcnow().isoformat())
    db.commit()


def _cancel_stale(db: Session):
    """Kuyrukta fazla bekleyen zaman-duyarlı postları iptal eder.
    Elle yazılan tweetler zaman-duyarlı değildir; asla bayatlamaz."""
    now = datetime.utcnow()
    q = db.query(models.Post).filter(models.Post.status.in_(["queued", "approved"]))
    for p in q.all():
        if p.kind == "manual":
            continue
        limit = STALE_MINUTES.get(p.kind)
        if limit is None:
            continue  # bilinmeyen tip: kendiliğinden iptal etme
        if p.created_at and now - p.created_at > timedelta(minutes=limit):
            p.status = "cancelled"
            p.error = "bayat (zamanında gönderilemedi)"
    db.commit()


def _resolve_reply_target(db: Session, post: models.Post,
                          match: models.Match | None) -> str | None:
    """Bu post hangi tweet'in altına gitmeli?

    Maç olayları: o maçın thread zincirinin son halkası.
    Diğer çok parçalı içerikler: reply_to_post_id ile işaret edilen postun
    tweet id'si (henüz gönderilmemişse yanıt verilmez, ana akışa düşer).
    """
    if match and post.is_reply and match.last_tweet_id:
        return match.last_tweet_id
    if post.reply_to_post_id:
        parent = db.get(models.Post, post.reply_to_post_id)
        if parent and parent.tweet_id and parent.tweet_id != "unknown":
            return parent.tweet_id
    return None


def _pick_post(db: Session, mode: str) -> models.Post | None:
    statuses = ["queued", "approved"] if mode == "auto" else ["approved"]
    posts = (
        db.query(models.Post)
        .filter(models.Post.status.in_(statuses))
        .all()
    )
    if not posts:
        return None
    # öncelik, sonra eskilik
    posts.sort(key=lambda p: (PRIORITY.get(p.kind, 5), p.created_at or datetime.utcnow()))

    # Görsel bekleyen kartlar: ht/ft/preview görselleri ~80 sn'de üretiliyor.
    # Görseli henüz yoksa kısa süre beklet; süre dolarsa metin olarak gider
    # (görsel hiçbir içeriği engellememeli).
    from app.content_engine.imagegen import IMAGE_KINDS, IMAGE_WAIT_MIN
    hazir = []
    for post in posts:
        if (post.kind in IMAGE_KINDS and post.match_id
                and not post.media_source_url):
            yas = (datetime.utcnow() - post.created_at).total_seconds() / 60
            if yas < IMAGE_WAIT_MIN:
                continue          # görsel üretimini bekle
        hazir.append(post)
    posts = hazir

    # RİTİM ŞERİDİNE GÖRE ELEME: her postun beklemesi kendi şeridine göre
    # (thread hızlı, ana akış yavaş). Bir postun şeridi henüz hazır değilse
    # ATLA ve sıradakine bak — yoksa yavaş şeritteki üst öncelikli bir tweet,
    # hazır bekleyen hızlı şerit tweetlerini de kilitliyordu (18.08).
    son = _last_post_at(db)
    gecen = (datetime.utcnow() - son).total_seconds() if son else 10**9

    for post in posts:
        if gecen < _required_gap(db, bool(post.is_reply)):
            continue             # bu şerit henüz hazır değil

        if not post.is_reply or not post.match_id:
            return post
        match = db.get(models.Match, post.match_id)
        if match and match.thread_tweet_id:
            return post          # kök gitmiş, yanıt verilebilir
        # kök henüz gönderilmemiş: kuyrukta bekliyor mu?
        root_pending = (
            db.query(models.Post.id)
            .filter(
                models.Post.match_id == post.match_id,
                models.Post.is_reply == False,  # noqa: E712
                models.Post.status.in_(["queued", "approved"]),
            )
            .first()
        )
        if root_pending:
            continue             # önce kök gitsin, bunu atla
        # Kök iptal olmuş/bayatlamış: bu tweet köke terfi etsin.
        post.is_reply = False
        db.commit()
        return post
    return None


def tick(db: Session) -> dict:
    """Tek dağıtım adımı. Scheduler bunu ~15 sn'de bir çağırır."""
    result = {"sent": 0, "skipped": None}

    if crud.get_setting(db, "posting_enabled", "false") != "true":
        result["skipped"] = "paylaşım kapalı"
        return result

    _cancel_stale(db)

    if not _within_active_hours(db):
        result["skipped"] = "aktif saat dışı"
        return result

    # _pick_post artık ritim şeridini de dikkate alıyor: yalnızca kendi
    # şeridi hazır olan, en yüksek öncelikli postu döndürür.
    mode = crud.get_setting(db, "automation_mode", "auto")
    post = _pick_post(db, mode)
    if not post:
        result["skipped"] = "kuyruk boş / ritim beklemesi"
        return result

    daily_cap = _get_int(db, "daily_post_cap", 120)
    account = xclient.pick_account(db, daily_cap)
    if not account:
        result["skipped"] = "uygun hesap yok"
        crud.add_log(db, "Gönderilecek uygun hesap yok (tavan/durum)", level="warn",
                     source="scheduler")
        return result

    # --- thread zinciri: maçın son tweetine yanıt ver
    match = db.get(models.Match, post.match_id) if post.match_id else None
    reply_to = _resolve_reply_target(db, post, match)

    # "skip" = görsel üretilemedi işareti; medya olarak kullanılmaz
    medya = post.media_source_url
    if medya in ("", "skip", None):
        medya = None
    tweet_id = xclient.post_tweet(db, account, post.content, reply_to=reply_to,
                                  quote_url=post.quote_url or None,
                                  media_url=medya)
    if tweet_id == "duplicate":
        post.status = "cancelled"
        post.error = "yinelenen tweet"
        db.commit()
        return result
    if tweet_id == "unknown":
        # X boş yanıt döndürdü: gönderim SESSİZCE reddedilmiş olabilir.
        # Başarı saymak içeriği kaybettiriyordu (17.08 canlı maç öncesi
        # ilk 11 tweeti böyle yok oldu, DB "gönderildi" diyordu).
        # Başarısız say -> dispatcher yeniden dener. Tweet aslında gittiyse
        # X mükerrer korumasi devreye girer ve post iptal edilir; yani
        # çift tweet riski yok, sessiz kayıp riski biter.
        post.error = (post.error or "") + "·"
        if post.error.count("·") >= 3:
            post.status = "failed"
            post.error += " X boş yanıt döndürdü (sessiz ret)"
        db.commit()
        crud.add_log(db, f"X boş yanıt döndürdü, yeniden denenecek: {post.content[:50]}",
                     level="warn", source="scheduler")
        # OTOMATİK FREN: art arda 3 sessiz ret = X'in davranış filtresi
        # devrede olabilir (17.08'de 226 bayrağının erken belirtisiydi).
        # Israr etmek durumu kötüleştirir; kendiliğinden dur.
        seri = int(crud.get_setting(db, "empty_response_streak", "0") or 0) + 1
        crud.set_setting(db, "empty_response_streak", str(seri))
        db.commit()
        if seri >= 3:
            crud.set_setting(db, "posting_enabled", "false")
            crud.set_setting(db, "empty_response_streak", "0")
            db.commit()
            crud.add_log(db, "OTOMATİK FREN: art arda 3 sessiz ret — "
                             "paylaşım kendiliğinden durduruldu",
                         level="error", source="scheduler")
        result["skipped"] = "boş yanıt (sessiz ret)"
        return result

    if tweet_id:
        post.status = "posted"
        post.account_id = account.id
        post.tweet_id = tweet_id
        post.posted_at = datetime.utcnow()
        # thread durumunu ilerlet
        if match and tweet_id != "unknown":
            if not match.thread_tweet_id:
                match.thread_tweet_id = tweet_id
            match.last_tweet_id = tweet_id
        db.commit()
        _mark_sent(db)
        crud.set_setting(db, "empty_response_streak", "0")  # başarı seriyi sıfırlar
        db.commit()
        crud.add_log(db, f"Tweet gönderildi (@{account.username}): {post.content[:60]}",
                     source="scheduler")
        result["sent"] = 1
    else:
        # gönderilemedi: birkaç deneme sonra başarısız işaretle
        post.error = (post.error or "") + "·"
        if post.error.count("·") >= 3:
            post.status = "failed"
        db.commit()
        result["skipped"] = "gönderim hatası"
    return result
