#!/usr/bin/env python3
"""
Auto-sync des domaines de sources WAStream depuis le paste protégé.

Le paste pastebin.com/fuPuHtni (mot de passe) liste les URLs de base courantes
des sources DDL (free-telecharger, movix, wawacity). Ces sites changent
régulièrement de domaine. Ce script :
  1. déverrouille le paste (POST du mot de passe + token CSRF),
  2. extrait les URLs et les associe aux variables du .env par marque,
  3. si un domaine a changé -> met à jour le .env (sauvegarde avant) et
     recrée le conteneur wastream.

Sans changement : ne touche à rien, ne redémarre rien (idempotent).
Zéro dépendance : stdlib uniquement. Prévu pour tourner en cron.

Usage:
  sync-domains.py            # applique les changements + redémarre si besoin
  sync-domains.py --dry-run  # affiche ce qui changerait, ne modifie/redémarre rien
"""
from __future__ import annotations

import html
import http.cookiejar
import os
import re
import shutil
import subprocess
import sys
import time
import urllib.parse
import urllib.request
from datetime import datetime, timezone

# ============================ Config ============================
PASTE_URL = "https://pastebin.com/fuPuHtni"
PASTE_PASSWORD = "wastream"

BASE_DIR = "/projets/wastream"
ENV_PATH = os.path.join(BASE_DIR, ".env")
COMPOSE_FILE = os.path.join(BASE_DIR, "docker-compose.yml")
LOG_PATH = os.path.join(BASE_DIR, "logs", "domain-sync.log")  # data/ appartient à root (conteneur)
DOCKER = "/usr/bin/docker"

UA = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
HTTP_TIMEOUT = 25

# Marque -> (variable .env, motifs de reconnaissance dans le nom d'hôte).
# Les sites gardent leur marque en changeant de TLD, donc on matche la marque.
BRANDS = [
    ("FREE_TELECHARGER_URL", ("free-telecharger", "free-tele", "freetele", "free_tele")),
    ("MOVIX_URL", ("movix",)),
    ("WAWACITY_URL", ("wawacity", "wawa")),
]

DRY_RUN = "--dry-run" in sys.argv


# ============================ Utils ============================
def log(msg: str) -> None:
    line = f"[{datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')}Z] {msg}"
    print(line, flush=True)
    try:
        os.makedirs(os.path.dirname(LOG_PATH), exist_ok=True)
        with open(LOG_PATH, "a", encoding="utf-8") as f:
            f.write(line + "\n")
    except OSError:
        pass


def normalize(url: str) -> str:
    return url.strip().rstrip("/")


def brand_for(url: str) -> str | None:
    host = (urllib.parse.urlparse(url).netloc or url).lower()
    for var, patterns in BRANDS:
        if any(p in host for p in patterns):
            return var
    return None


# ======================== Récupération paste ========================
def fetch_paste() -> str:
    """Déverrouille le paste et renvoie son texte brut (une URL par ligne)."""
    cj = http.cookiejar.CookieJar()
    opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
    opener.addheaders = [("User-Agent", UA)]

    # 1) page + cookies + token CSRF
    with opener.open(PASTE_URL, timeout=HTTP_TIMEOUT) as r:
        page = r.read().decode("utf-8", "replace")

    m = re.search(r'name="_csrf-frontend"[^>]*value="([^"]+)"', page)
    if not m:
        raise RuntimeError("token CSRF introuvable (structure pastebin changée ?)")
    token = m.group(1)

    # 2) POST du mot de passe
    data = urllib.parse.urlencode({
        "_csrf-frontend": token,
        "PostPasswordVerificationForm[password]": PASTE_PASSWORD,
    }).encode()
    req = urllib.request.Request(PASTE_URL, data=data, headers={
        "User-Agent": UA,
        "Referer": PASTE_URL,
        "Content-Type": "application/x-www-form-urlencoded",
    })
    with opener.open(req, timeout=HTTP_TIMEOUT) as r:
        unlocked = r.read().decode("utf-8", "replace")

    # 3) reconstruire le texte depuis <ol class="text"> ... <li> ... </li>
    block = re.search(r'<ol[^>]*class="text"[^>]*>(.*?)</ol>', unlocked, re.S)
    if not block:
        raise RuntimeError("contenu verrouillé (mauvais mot de passe ?) ou HTML changé")
    lines = re.findall(r"<li[^>]*>(.*?)</li>", block.group(1), re.S)
    out = []
    for li in lines:
        txt = html.unescape(re.sub(r"<[^>]+>", "", li)).strip()
        if txt:
            out.append(txt)
    return "\n".join(out)


# ======================== Mise à jour .env ========================
def read_env(path: str) -> list[str]:
    with open(path, encoding="utf-8") as f:
        return f.read().splitlines()


def current_value(lines: list[str], key: str) -> str | None:
    pat = re.compile(rf"^\s*{re.escape(key)}\s*=\s*(.*)$")
    for ln in lines:
        m = pat.match(ln)
        if m:
            return m.group(1).strip()
    return None


def set_value(lines: list[str], key: str, value: str) -> list[str]:
    pat = re.compile(rf"^(\s*){re.escape(key)}\s*=.*$")
    done = False
    new = []
    for ln in lines:
        if not done and pat.match(ln):
            new.append(f"{key}={value}")
            done = True
        else:
            new.append(ln)
    if not done:
        new.append(f"{key}={value}")
    return new


def write_env_atomic(path: str, lines: list[str]) -> None:
    tmp = path + ".tmp"
    with open(tmp, "w", encoding="utf-8") as f:
        f.write("\n".join(lines) + "\n")
    os.replace(tmp, path)


def restart_container() -> None:
    subprocess.run(
        [DOCKER, "compose", "-f", COMPOSE_FILE, "up", "-d", "--force-recreate"],
        check=True, capture_output=True, text=True,
    )


# ============================ Main ============================
def main() -> int:
    try:
        text = fetch_paste()
    except Exception as e:
        log(f"ERREUR récupération paste: {type(e).__name__}: {e} -> aucune modif")
        return 1

    urls = [u for u in (normalize(l) for l in text.splitlines()) if u.startswith("http")]
    if not urls:
        log("ERREUR: aucune URL dans le paste -> aucune modif (sécurité)")
        return 1

    # associer chaque URL à sa variable
    desired: dict[str, str] = {}
    for u in urls:
        var = brand_for(u)
        if var is None:
            log(f"AVERTISSEMENT: URL non reconnue, ignorée: {u}")
            continue
        desired[var] = u

    lines = read_env(ENV_PATH)
    changes: list[tuple[str, str, str]] = []
    for var, new_url in desired.items():
        old = current_value(lines, var)
        if normalize(old or "") != new_url:
            changes.append((var, old or "(vide)", new_url))

    if not changes:
        log(f"OK: aucun changement de domaine ({len(desired)} sources vérifiées)")
        return 0

    summary = "; ".join(f"{v}: {o} -> {n}" for v, o, n in changes)
    if DRY_RUN:
        log(f"[DRY-RUN] {len(changes)} changement(s): {summary}")
        return 0

    # sauvegarde puis écriture
    backup = ENV_PATH + ".bak-" + datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S")
    shutil.copy2(ENV_PATH, backup)
    for var, _o, n in changes:
        lines = set_value(lines, var, n)
    write_env_atomic(ENV_PATH, lines)
    log(f"{len(changes)} changement(s) appliqué(s): {summary} (backup: {os.path.basename(backup)})")

    # redémarrage
    try:
        restart_container()
        log("Conteneur wastream recréé avec les nouveaux domaines.")
    except subprocess.CalledProcessError as e:
        log(f"ERREUR redémarrage docker: {e.stderr.strip()[:300]}")
        return 1
    return 0


if __name__ == "__main__":
    sys.exit(main())
