#!/usr/bin/env python3
"""
SYLink HoneyBot — service polling activation.

Polle toutes les 15 s /api/honeypot/enroll/poll-activation?token=<token>.
Quand le backend renvoie `activated=true` :
  1. Persiste license.json (license_token + tenant_id + honeypot_id)
  2. Applique config réseau si network_config présent (netplan generate/apply)
  3. Persiste hostname si imposé
  4. Lance `reboot` — au prochain boot, init.py voit license.json et pose
     le banner « couverture » (BackupServer Corp) au lieu du banner d'activation.

Service systemd : Type=simple, Restart=always, lancé après sylink-honeypot-init.
"""
from __future__ import annotations

import json
import os
import socket
import subprocess
import sys
import time
import urllib.request
import urllib.parse
from datetime import datetime, timezone
from pathlib import Path

CONFIG_DIR = Path("/opt/sylink-honeypot/etc")
LOG_DIR = Path("/opt/sylink-honeypot/log")
PT_API_BASE = os.environ.get("HP_API_BASE", "https://api.unisoc.fr")
POLL_INTERVAL = int(os.environ.get("HP_POLL_INTERVAL", "15"))


def log(msg: str):
    LOG_DIR.mkdir(parents=True, exist_ok=True)
    line = f"[{datetime.now(timezone.utc).isoformat()}] {msg}"
    print(line, flush=True)
    with (LOG_DIR / "poll.log").open("a") as f:
        f.write(line + "\n")


def read_enroll() -> dict | None:
    f = CONFIG_DIR / "enroll.json"
    if not f.exists():
        return None
    try:
        return json.loads(f.read_text())
    except Exception:
        return None


def already_activated() -> bool:
    f = CONFIG_DIR / "license.json"
    if not f.exists():
        return False
    try:
        return bool(json.loads(f.read_text()).get("license_token"))
    except Exception:
        return False


def poll_once(token: str) -> dict | None:
    url = f"{PT_API_BASE}/api/honeypot/enroll/poll-activation?token={urllib.parse.quote(token)}"
    try:
        req = urllib.request.Request(url, method="GET")
        with urllib.request.urlopen(req, timeout=10) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        log(f"poll HTTP {e.code} : {e.reason}")
        return None
    except Exception as e:
        log(f"poll erreur : {e}")
        return None


def write_license(payload: dict):
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    license_doc = {
        "license_token": payload["license_token"],
        "tenant_id": payload.get("tenant_id"),
        "honeypot_id": payload.get("honeypot_id"),
        "activated_at": payload.get("activated_at") or datetime.now(timezone.utc).isoformat(),
    }
    (CONFIG_DIR / "license.json").write_text(json.dumps(license_doc, indent=2))
    os.chmod(CONFIG_DIR / "license.json", 0o600)
    log(f"license.json écrit (honeypot_id={license_doc['honeypot_id']})")


def apply_hostname(hostname: str | None):
    if not hostname:
        return
    try:
        subprocess.run(["hostnamectl", "set-hostname", hostname], check=True, timeout=10)
        log(f"hostname défini : {hostname}")
    except Exception as e:
        log(f"hostnamectl échec : {e}")


def apply_netplan(network_config: dict | None):
    """Génère un yaml netplan + apply. Mode `dhcp` ne change rien (laisse le netplan
    existant). Mode `static` écrit /etc/netplan/99-unisoc-honeypot.yaml et apply."""
    if not network_config:
        return
    mode = network_config.get("mode", "dhcp")
    if mode != "static":
        log("netplan: mode dhcp, rien à changer")
        return

    iface = network_config.get("interface") or "ens192"
    ip_cidr = network_config.get("ip_cidr")
    gateway = network_config.get("gateway")
    dns = network_config.get("dns") or ["8.8.8.8", "1.1.1.1"]

    if not ip_cidr or not gateway:
        log(f"netplan: static incomplet (ip={ip_cidr} gw={gateway}) — fallback DHCP")
        return

    yaml = f"""# UniSOC honeypot — config statique apposée à l'activation
network:
  version: 2
  ethernets:
    {iface}:
      dhcp4: no
      addresses:
        - {ip_cidr}
      routes:
        - to: default
          via: {gateway}
      nameservers:
        addresses: [{', '.join(dns)}]
"""
    netplan_file = Path("/etc/netplan/99-unisoc-honeypot.yaml")
    netplan_file.write_text(yaml)
    os.chmod(netplan_file, 0o600)
    log(f"netplan: yaml écrit {netplan_file}")

    # Désactive les autres netplan files DHCP pour éviter les conflits
    for f in Path("/etc/netplan").glob("*.yaml"):
        if f.name != "99-unisoc-honeypot.yaml":
            f.rename(f.with_suffix(".yaml.unisoc-bak"))
            log(f"netplan: désactivé {f.name}")

    try:
        subprocess.run(["netplan", "generate"], check=True, timeout=15)
        # netplan apply sera fait au reboot pour éviter de couper le polling avant reboot
        log("netplan: generate OK")
    except Exception as e:
        log(f"netplan generate échec : {e}")


def trigger_reboot():
    log("=== REBOOT demandé dans 5s ===")
    time.sleep(5)
    try:
        subprocess.run(["systemctl", "reboot"], timeout=10)
    except Exception as e:
        log(f"reboot échec : {e}")
        # fallback brutal
        os.system("reboot")


def main() -> int:
    log("== sylink-honeypot-poll start ==")

    enroll = read_enroll()
    if not enroll or not enroll.get("token"):
        log("ERREUR : enroll.json absent — l'init a-t-il tourné ?")
        return 1

    token = enroll["token"]
    if already_activated():
        log("VM déjà activée — rien à faire, je sors.")
        return 0

    log(f"polling toutes les {POLL_INTERVAL}s sur {PT_API_BASE}")
    while True:
        if already_activated():
            log("license.json apparu hors poll — exit.")
            return 0

        resp = poll_once(token)
        if resp and resp.get("activated"):
            log(f"ACTIVATION reçue : tenant={resp.get('tenant_id')} hp_id={resp.get('honeypot_id')}")
            write_license(resp)
            apply_hostname(resp.get("hostname"))
            apply_netplan(resp.get("network_config"))
            # Re-run l'init.py pour basculer le banner (mais le reboot va tout réinitialiser)
            try:
                subprocess.run(["/opt/sylink-honeypot/bin/init.py"], timeout=20)
            except Exception:
                pass
            trigger_reboot()
            return 0
        time.sleep(POLL_INTERVAL)


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