"""
Point d'entrée du crawler. Lance le scheduler et exécute les jobs périodiques.

Jobs :
  - Toutes les SCHED_INCREMENTAL_H heures : crawl incrémental (additions + modifications)
  - Toutes les SCHED_ENRICH_H heures      : enrichissement des bands non enrichies
  - Filet de sécurité (calcul glissant)   : crawl complet Europe si plus de
    CRAWLER_FULL_CRAWL_INTERVAL_DAYS jours depuis le dernier (checkpoint
    last_full_crawl_at), vérifié chaque jour à 03:00 UTC + au démarrage

Variables d'env :
  DATABASE_URL                     (obligatoire)
  FLARESOLVERR_URL                 (défaut: http://flaresolverr:8191)
  CRAWLER_SCHED_INCREMENTAL_H      (défaut: 4)
  CRAWLER_SCHED_ENRICH_H           (défaut: 1)
  CRAWLER_FULL_CRAWL_INTERVAL_DAYS (défaut: 60, mettre 0 pour désactiver)
"""
import logging
import sys
import time
from datetime import UTC, datetime

import schedule

from .config import (
    ENRICH_LIMIT,
    FLARESOLVERR_URL,
    FS_TIMEOUT_MS,
    FULL_CRAWL_INTERVAL_DAYS,
    SCHED_ENRICH_H,
    SCHED_INCREMENTAL_H,
)
from .db import claim_job_trigger, finish_job_trigger, get_checkpoint
from .flaresolverr import FlareSolverr
from .health import database_probe, freshness_probe, http_probe, maybe_run
from .jobs import run_enrich, run_full_crawl, run_incremental
from .ma_http import MASession

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    datefmt="%Y-%m-%dT%H:%M:%S",
    stream=sys.stdout,
)
log = logging.getLogger(__name__)


def _wait_flaresolverr(fs: FlareSolverr, max_wait: int = 120):
    log.info("[main] waiting for FlareSolverr...")
    for _ in range(max_wait // 5):
        if fs.healthy():
            log.info("[main] FlareSolverr is up")
            return
        time.sleep(5)
    raise RuntimeError("FlareSolverr not reachable after timeout")


def main():
    log.info("[main] crawler starting")
    fs = FlareSolverr(FLARESOLVERR_URL, timeout_ms=FS_TIMEOUT_MS)
    _wait_flaresolverr(fs)

    ma = MASession(fs)

    # ------------------------------------------------------------------
    # Helpers pour lancer les jobs avec log
    # ------------------------------------------------------------------

    def job_incremental():
        log.info("[scheduler] → incremental additions")
        run_incremental(ma, "created")
        log.info("[scheduler] → incremental modifications")
        run_incremental(ma, "modified")

    def job_enrich():
        log.info("[scheduler] → enrich")
        run_enrich(ma, limit=ENRICH_LIMIT)

    def job_full():
        if not FULL_CRAWL_INTERVAL_DAYS:
            return
        last = get_checkpoint("last_full_crawl_at")
        if last:
            try:
                last_dt = datetime.strptime(last, "%Y-%m-%d").replace(tzinfo=UTC)
                elapsed_days = (datetime.now(UTC) - last_dt).days
                if elapsed_days < FULL_CRAWL_INTERVAL_DAYS:
                    return
            except ValueError:
                pass  # checkpoint mal formé, on relance par sécurité
        log.info(f"[scheduler] → full Europe crawl (filet {FULL_CRAWL_INTERVAL_DAYS}j écoulé)")
        run_full_crawl(ma)

    # ------------------------------------------------------------------
    # Planification
    # ------------------------------------------------------------------

    if SCHED_INCREMENTAL_H > 0:
        schedule.every(SCHED_INCREMENTAL_H).hours.do(job_incremental)
        log.info(f"[main] incremental scheduled every {SCHED_INCREMENTAL_H}h")

    if SCHED_ENRICH_H > 0:
        schedule.every(SCHED_ENRICH_H).hours.do(job_enrich)
        log.info(f"[main] enrich scheduled every {SCHED_ENRICH_H}h")

    if FULL_CRAWL_INTERVAL_DAYS > 0:
        # Vérifié chaque jour à 03:00 UTC ; ne se déclenche que si l'intervalle est écoulé
        schedule.every().day.at("03:00").do(job_full)
        log.info(f"[main] full crawl: filet de {FULL_CRAWL_INTERVAL_DAYS}j, vérifié chaque jour à 03:00 UTC")

    # Premier run au démarrage (job_full rattrape un redémarrage qui aurait loupé la fenêtre)
    log.info("[main] running initial jobs at startup")
    job_incremental()
    job_enrich()
    job_full()

    log.info("[main] entering scheduler loop")
    while True:
        schedule.run_pending()
        _check_job_triggers(ma)
        _check_health()
        time.sleep(60)


def _check_health():
    """Contrôle horaire : base joignable, FlareSolverr joignable, données fraîches."""
    import requests

    from .config import FLARESOLVERR_URL
    from .db import get_conn, log_event

    maybe_run(
        get_conn,
        "crawler",
        [
            ("database", database_probe(get_conn)),
            # FlareSolverr est indispensable au crawl : injoignable, plus aucune
            # page de Metal Archives n'est récupérable.
            ("flaresolverr", http_probe(requests.get, f"{FLARESOLVERR_URL}/health")),
            # Le crawl incrémental tourne toutes les 4 h : au-delà de 12 h sans
            # nouveau groupe vu, quelque chose est bloqué en amont.
            ("fraicheur_donnees", freshness_probe(
                get_conn,
                "SELECT max(started_at) FROM crawl_run WHERE status = 'done'",
                12, "dernier run terminé")),
        ],
        log_event=lambda lvl, msg: log_event(lvl, msg),
    )


def _check_job_triggers(ma: "MASession"):
    """Consomme les job_triggers en attente créés depuis l'admin."""
    result = claim_job_trigger()
    if not result:
        return
    trigger_id, job_type = result
    log.info(f"[main] job trigger #{trigger_id}: {job_type}")
    error = None
    try:
        if job_type == "enrich":
            run_enrich(ma, limit=ENRICH_LIMIT)
        elif job_type == "incremental":
            run_incremental(ma, "created")
            run_incremental(ma, "modified")
        elif job_type == "full_crawl":
            run_full_crawl(ma)
        else:
            error = f"job_type inconnu: {job_type}"
            log.warning(f"[main] {error}")
    except Exception as e:
        error = str(e)
        log.error(f"[main] job trigger #{trigger_id} error: {e}", exc_info=True)
    finally:
        finish_job_trigger(trigger_id, error)


if __name__ == "__main__":
    main()
