#!/usr/bin/env bash
set -euo pipefail

if [ "$(id -u)" -ne 0 ]; then
  echo "Start deze installer met sudo/root."
  exit 1
fi

DEFAULT_PORTAL_URL="${REMOTE_PLATFORM_URL:-https://remote.no-li.nl}"
INSTALL_VERSION="0.9.0"

cat >/tmp/rp_installer.py <<'END_RP_INSTALLER'
import json, os, platform, getpass, subprocess, urllib.request, urllib.error, time, ipaddress, shutil
from pathlib import Path

PORTAL_URL = os.environ.get("REMOTE_PLATFORM_URL", "https://remote.no-li.nl").rstrip("/")
INSTALL_VERSION = "0.9.0"
AGENT_BIN = Path("/usr/local/bin/gateway-agent")
CONFIG_DIR = Path("/etc/remote-platform/agent")
CONFIG_FILE = CONFIG_DIR / "config.json"
SERVICE_FILE = Path("/etc/systemd/system/remote-platform-agent.service")
OLD_SERVICE_FILE = Path("/etc/systemd/system/gateway-agent.service")
WG_CLIENT = Path("/etc/wireguard/rpwg-client.conf")

# NIEUW: niet-interactieve herstel-modus, bedoeld om vanuit de portal/agent
# zelf getriggerd te worden (geen mens die op een prompt zit te wachten).
# ask() geeft dan meteen de default terug in plaats van op invoer te wachten --
# de rest van het script hoeft daardoor NIET herschreven te worden, elke
# bestaande prompt "doet gewoon het voor herstel voor de hand liggende ding".
NONINTERACTIVE = os.environ.get("RP_NONINTERACTIVE", "") == "1"

def ask(prompt, default=None, secret=False):
    if NONINTERACTIVE:
        return default or ""
    suffix = f" [{default}]" if default not in (None, "") else ""
    v = getpass.getpass(f"{prompt}{suffix}: ") if secret else input(f"{prompt}{suffix}: ")
    v = v.strip()
    return v if v else (default or "")

def req(method, url, token=None, data=None):
    body = json.dumps(data).encode("utf-8") if data is not None else None
    r = urllib.request.Request(url, data=body, method=method)
    if token:
        r.add_header("Authorization", "Bearer " + token)
    if data is not None:
        r.add_header("Content-Type", "application/json")
    with urllib.request.urlopen(r) as x:
        payload = x.read()
        return json.loads(payload.decode("utf-8")) if payload else {}

def run(cmd, check=True):
    print("+", cmd)
    return subprocess.run(cmd, shell=True, check=check)

def output(cmd):
    try:
        return subprocess.check_output(cmd, shell=True, text=True, stderr=subprocess.DEVNULL)
    except Exception:
        return ""

def service_exists(name):
    return subprocess.run(f"systemctl list-unit-files {name} --no-legend 2>/dev/null | grep -q '^{name}'", shell=True).returncode == 0

def stop_services():
    for svc in ["remote-platform-agent.service", "gateway-agent.service", "wg-quick@rpwg-client.service"]:
        run(f"systemctl stop {svc} 2>/dev/null || true", check=False)

def disable_services():
    for svc in ["remote-platform-agent.service", "gateway-agent.service", "wg-quick@rpwg-client.service"]:
        run(f"systemctl disable {svc} 2>/dev/null || true", check=False)

def load_existing_config():
    if not CONFIG_FILE.exists():
        return {}
    try:
        return json.loads(CONFIG_FILE.read_text())
    except Exception:
        return {}

def backup_existing_config():
    if CONFIG_FILE.exists():
        backup = CONFIG_FILE.with_suffix(".json.backup-" + time.strftime("%Y%m%d-%H%M%S"))
        shutil.copy2(CONFIG_FILE, backup)
        print("Bestaande config backup:", backup)

def find_existing_gateway_id(cfg, token):
    gid = cfg.get("gateway_id") or cfg.get("gatewayId") or ""
    if gid:
        return gid
    serial = cfg.get("gateway_serial") or cfg.get("serial")
    if serial:
        try:
            gateways = req("GET", PORTAL_URL + "/gateways", token=token)
            for g in gateways:
                if g.get("serial") == serial:
                    return g.get("id") or ""
        except Exception as e:
            print("Kon gateway niet op serial zoeken:", e)
    return ""

def existing_markers():
    markers = []
    if AGENT_BIN.exists(): markers.append(str(AGENT_BIN))
    if CONFIG_FILE.exists(): markers.append(str(CONFIG_FILE))
    if SERVICE_FILE.exists() or service_exists("remote-platform-agent.service"): markers.append("remote-platform-agent.service")
    if OLD_SERVICE_FILE.exists() or service_exists("gateway-agent.service"): markers.append("gateway-agent.service")
    if WG_CLIENT.exists(): markers.append(str(WG_CLIENT))
    return markers

def remove_local_files(remove_config_dir=True):
    stop_services()
    disable_services()
    backup_existing_config()

    for f in [SERVICE_FILE, OLD_SERVICE_FILE]:
        if f.exists():
            f.unlink()

    if AGENT_BIN.exists():
        AGENT_BIN.unlink()

    if WG_CLIENT.exists():
        WG_CLIENT.unlink()

    if remove_config_dir and CONFIG_DIR.exists():
        shutil.rmtree(CONFIG_DIR, ignore_errors=True)

    run("systemctl daemon-reload", check=False)

def choose_existing_action(token):
    markers = existing_markers()
    cfg = load_existing_config()
    if not markers:
        if NONINTERACTIVE:
            raise SystemExit("Niet-interactieve herstel-modus vereist een bestaande installatie -- die is hier niet gevonden.")
        return "new", cfg
    print("\nBestaande installatie gevonden:")
    for m in markers:
        print(" -", m)
    print("\nKies actie:")
    print("1. Herstellen/updaten bestaande installatie")
    print("2. Opnieuw installeren")
    print("3. Volledig verwijderen (ook uit portal indien gevonden)")
    print("4. Afbreken")
    choice = ask("Keuze", "1")
    if choice == "1":
        stop_services()
        if AGENT_BIN.exists(): AGENT_BIN.unlink()
        return "repair", cfg
    if choice == "2":
        remove_local_files(remove_config_dir=False)
        return "reinstall", cfg
    if choice == "3":
        gid = find_existing_gateway_id(cfg, token)
        if gid:
            confirm = ask(f"Type VERWIJDER om gateway {gid} ook uit portal te verwijderen", "")
            if confirm == "VERWIJDER":
                req("DELETE", PORTAL_URL + f"/gateways/{gid}", token=token)
                print("Gateway uit portal verwijderd:", gid)
        else:
            print("Geen bestaande gateway_id gevonden; portal verwijderen overgeslagen.")
        remove_local_files()
        raise SystemExit("Volledig verwijderd.")
    raise SystemExit("Afgebroken.")

def detect_subnets(include_advanced=False):
    # Geeft nu (netwerk, interface)-paren terug in plaats van alleen het
    # netwerk -- nodig om straks automatisch de juiste lan_interface voor
    # NAT/masquerade te kunnen meesturen, in plaats van dat handmatig achteraf
    # te moeten repareren (zoals bij TvKantoor en S010HMI gebeurde).
    out = output("ip -4 route show scope link")
    found = []
    for line in out.splitlines():
        parts = line.split()
        if not parts: continue
        net = parts[0]
        dev = ""
        if "dev" in parts:
            try: dev = parts[parts.index("dev")+1]
            except Exception: dev = ""
        if net == "default" or net.startswith("169.254.") or "/" not in net:
            continue
        if not include_advanced:
            if dev.startswith(("docker", "br-", "veth", "wg", "rpwg")): continue
            if net.startswith(("172.17.", "172.18.", "172.19.", "10.99.")): continue
        try:
            n = ipaddress.ip_network(net, strict=False)
        except Exception:
            continue
        if n.is_loopback or n.is_link_local: continue
        s = str(n)
        if s not in [f[0] for f in found]:
            found.append((s, dev))
    return found


def choose_reuse_gateway(old_cfg):
    gid = old_cfg.get("gateway_id") or old_cfg.get("gatewayId") or ""
    if not gid:
        return ""
    print("\nBestaande gateway gevonden in config:")
    print(gid)
    print("\nKies actie:")
    print("1. Deze bestaande gateway hergebruiken")
    print("2. Nieuwe gateway aanmaken")
    c = ask("Keuze", "1")
    if c == "1":
        return gid
    return ""

def choose_subnets():
    advanced = ask("Geavanceerde/VPN/Docker netwerken tonen? j/n", "n").lower().startswith("j")
    detected = detect_subnets(include_advanced=advanced)  # lijst van (netwerk, interface)
    print("\nGevonden netwerken:")
    if detected:
        for i, (net, dev) in enumerate(detected, 1): print(f"{i}. {net} (via {dev or '?'})")
    else:
        print("Geen bruikbare IPv4-linknetwerken gevonden.")

    selected = []
    if NONINTERACTIVE:
        # Herstel-modus: gewoon alles wat nu gedetecteerd wordt opnieuw
        # meenemen, geen mens die hoeft te kiezen.
        selected = list(detected)
    else:
        if len(detected) == 1:
            choice = ask("Selecteer subnetnummer(s), komma-gescheiden", "1")
        elif len(detected) > 1:
            choice = ask("Selecteer subnetnummer(s), komma-gescheiden. Leeg = niets selecteren", "")
        else:
            choice = ""
        for item in [x.strip() for x in choice.split(",") if x.strip()]:
            selected.append(detected[int(item)-1])
        while True:
            manual = ask("Handmatig subnet toevoegen? voorbeeld 10.10.10.0/24, leeg = klaar", "")
            if not manual: break
            try:
                net = str(ipaddress.ip_network(manual, strict=False))
                selected.append((net, ""))  # interface onbekend voor handmatig ingevoerde subnetten
            except Exception as e:
                print("Ongeldig subnet:", e)

    out = []
    seen = set()
    for net, dev in selected:
        if net not in seen:
            seen.add(net)
            out.append((net, dev))
    if not out: raise SystemExit("Er is geen subnet gekozen.")
    return out

def choose_org(options):
    orgs = options.get("organizations", [])
    if not orgs: raise SystemExit("Geen klanten beschikbaar voor deze gebruiker.")
    print("\nKlanten:")
    for i,o in enumerate(orgs,1): print(f"{i}. {o['name']}")
    if options.get("can_create_organization"): print("N. Nieuwe klant")
    c = ask("Kies klantnummer" + (" of N" if options.get("can_create_organization") else ""))
    if c.lower()=="n":
        if not options.get("can_create_organization"): raise SystemExit("Deze gebruiker mag geen klanten aanmaken.")
        return req("POST", PORTAL_URL+"/installer/organizations", token=TOKEN, data={"name":ask("Nieuwe klantnaam")})["id"]
    return orgs[int(c)-1]["id"]

def choose_site(options, org_id):
    sites = [s for s in options.get("sites", []) if s["organization_id"] == org_id]
    print("\nLocatie:")
    print("0. Geen locatie / direct onder klant")
    if len(sites)==1:
        print(f"1. {sites[0]['name']} (voorgesteld)")
        print("N. Nieuwe locatie")
        c = ask("Kies locatie", "1")
    elif len(sites)>1:
        for i,s in enumerate(sites,1): print(f"{i}. {s['name']}")
        print("N. Nieuwe locatie")
        c = ask("Kies locatie")
    else:
        print("Geen locaties gevonden.")
        print("N. Nieuwe locatie")
        c = ask("Kies 0 of N", "0")
    if c == "0": return None
    if c.lower()=="n":
        return req("POST", PORTAL_URL+"/installer/sites", token=TOKEN, data={"organization_id":org_id,"name":ask("Nieuwe locatienaam")})["id"]
    return sites[int(c)-1]["id"]


def register_wayvnc_service(gw_id, token):
    # NIEUW: als wayvnc lokaal gedetecteerd en compatibel gemaakt is, meteen
    # een service in de portal registreren zodat 'ie direct bruikbaar is --
    # in plaats van dat iemand dat later handmatig moet toevoegen.
    try:
        req("POST", PORTAL_URL + f"/gateways/{gw_id}/services", token=token, data={
            "service_type": "vnc_local",
            "name": "wayvnc (lokaal scherm)",
            "target_host": "127.0.0.1",
            "target_port": 5900,
            "enabled": True,
            "auth_enabled": False,
            "username": None,
            "password": None,
            "view_only": False,
            "shared_session": True,
            "settings": {}
        })
        print("wayvnc-service geregistreerd in de portal.")
    except Exception as e:
        print("wayvnc-service registreren mislukt (ga verder zonder):", e)


def configure_wayvnc_compat_if_needed(gw_id=None, token=None):
    has_wayvnc = subprocess.run("command -v wayvnc >/dev/null 2>&1", shell=True).returncode == 0
    running = subprocess.run("pgrep -x wayvnc >/dev/null 2>&1", shell=True).returncode == 0
    if not has_wayvnc or not running:
        return

    print("\nwayvnc gedetecteerd.")
    print("Standaard wayvnc kan in noVNC de fout geven: Unsupported security types (types: 262).")
    print("Remote Platform kan wayvnc lokaal-only noVNC-compatible zetten:")
    print(" - bind op 127.0.0.1")
    print(" - poort 5900")
    print(" - auth uit, omdat toegang via gateway-tunnel loopt")
    choice = ask("wayvnc compatibel maken voor browser/noVNC? j/n", "j")
    if not choice.lower().startswith("j"):
        return

    script_url = PORTAL_URL + "/downloads/tools/fix-wayvnc-novnc.sh"
    tmp_script = "/tmp/fix-wayvnc-novnc.sh"
    applied = False
    try:
        urllib.request.urlretrieve(script_url, tmp_script)
        run(f"chmod +x {tmp_script}")
        run(f"{tmp_script}", check=False)
        applied = True
    except Exception as e:
        print("wayvnc compatibility stap mislukt:", e)

    if applied and gw_id:
        register_wayvnc_service(gw_id, token)


def install_service():
    SERVICE_FILE.write_text("""[Unit]
Description=Remote Platform Gateway Agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/gateway-agent -config /etc/remote-platform/agent/config.json
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
""")
    run("systemctl daemon-reload")
    run("systemctl enable --now remote-platform-agent.service")

print("Remote Platform Gateway Installer", INSTALL_VERSION, "(niet-interactief)" if NONINTERACTIVE else "")
print("Portal:", PORTAL_URL)

TOKEN = None
if not NONINTERACTIVE:
    print("\nInloggen")
    username = ask("Gebruiker/email")
    password = ask("Wachtwoord", secret=True)
    try:
        login = req("POST", PORTAL_URL+"/installer/login", data={"username":username,"password":password})
    except urllib.error.HTTPError as e:
        raise SystemExit(f"Login mislukt: HTTP {e.code}")
    TOKEN = login["access_token"]

mode, old_cfg = choose_existing_action(token=TOKEN)
reuse_gateway_id = choose_reuse_gateway(old_cfg) if mode in ("repair", "reinstall") else ""

if reuse_gateway_id:
    org_id = None
    site_id = None
    gateway_name = ask("Gateway naam", platform.node())
else:
    options = req("GET", PORTAL_URL+"/installer/options", token=TOKEN)
    org_id = choose_org(options)
    options = req("GET", PORTAL_URL+"/installer/options", token=TOKEN)
    site_id = choose_site(options, org_id)
    gateway_name = ask("Gateway naam", platform.node())

subnet_pairs = choose_subnets()          # lijst van (netwerk, interface)
subnets = [s[0] for s in subnet_pairs]
primary_subnet = subnets[0]
# NIEUW: verzamel ALLE unieke interfaces die bij de gekozen subnetten horen
# -- niet alleen die van het eerste subnet. Anders krijgt bijvoorbeeld
# wlan0 geen NAT-regel als eth0 toevallig als eerste is gekozen, terwijl
# beide netwerken wel degelijk bereikbaar moeten zijn (precies wat er bij
# S010HMI misging: 192.168.1.x via wlan0 werkte niet omdat alleen eth0
# een regel kreeg).
lan_interfaces = []
for _, dev in subnet_pairs:
    if dev and dev not in lan_interfaces:
        lan_interfaces.append(dev)

print("\nSamenvatting:")
print("Actie:", mode)
print("Gateway:", gateway_name)
if reuse_gateway_id:
    print("Hergebruik gateway_id:", reuse_gateway_id)
else:
    print("Locatie:", site_id or "direct onder klant")
print("Subnets:", ", ".join(subnets))
print("LAN-interface(s) (voor NAT):", ", ".join(lan_interfaces) if lan_interfaces else "(onbekend -- NAT wordt overgeslagen)")
if not ask("Installeren? j/n", "j").lower().startswith("j"):
    raise SystemExit("Afgebroken.")

arch = platform.machine().lower()
if arch in ("aarch64","arm64"): bin_url = PORTAL_URL+"/downloads/bin/gateway-agent-linux-arm64"
elif arch in ("x86_64","amd64"): bin_url = PORTAL_URL+"/downloads/bin/gateway-agent-linux-amd64"
else: raise SystemExit("Niet ondersteunde architectuur: "+arch)

# 'iptables' stond hier niet bij -- zonder dat pakket kan NAT/masquerade
# nooit ingesteld worden, en bleef elke nieuwe gateway zonder verkeer naar
# het echte klantnetwerk zitten totdat iemand dat achteraf handmatig fixte.
run("apt-get update && apt-get install -y curl ca-certificates wireguard python3 iproute2 iptables")
tmp_bin = "/tmp/gateway-agent.download"
print("Agent downloaden:", bin_url)
urllib.request.urlretrieve(bin_url, tmp_bin)
run(f"install -m 0755 {tmp_bin} /usr/local/bin/gateway-agent")
run(f"rm -f {tmp_bin}", check=False)

if reuse_gateway_id:
    gw = {
        "id": reuse_gateway_id,
        "serial": old_cfg.get("gateway_serial") or old_cfg.get("serial") or ("GW-REUSE-" + reuse_gateway_id[:8])
    }
    cfg = dict(old_cfg)
    cfg["server_url"] = PORTAL_URL
    cfg["gateway_id"] = reuse_gateway_id
    cfg["machine_subnet"] = primary_subnet
    if "gateway_serial" not in cfg:
        cfg["gateway_serial"] = gw["serial"]
else:
    reg = req("POST", PORTAL_URL+"/installer/register-gateway", token=TOKEN, data={
        "organization_id": org_id,
        "site_id": site_id,
        "gateway_name": gateway_name,
        "machine_subnet": primary_subnet,
        "hostname": platform.node(),
        "os": "linux",
        "arch": arch,
        "installer_version": INSTALL_VERSION
    })
    gw = reg["gateway"]
    cfg = reg["agent_config"]
    cfg["server_url"] = PORTAL_URL
    cfg["gateway_id"] = gw["id"]

CONFIG_DIR.mkdir(parents=True, exist_ok=True)
CONFIG_FILE.write_text(json.dumps(cfg, indent=2))
run("chmod 600 /etc/remote-platform/agent/config.json")
install_service()
configure_wayvnc_compat_if_needed(gw_id=gw["id"], token=TOKEN)

for net in subnets:
    try:
        req("POST", PORTAL_URL+f"/gateways/{gw['id']}/routes", token=TOKEN, data={"network":net,"description":"Selected during installer","source":"installer","published":True,"enabled":True})
    except Exception as e:
        print("Route registratie overgeslagen/fout:", net, e)

# NIEUW: handmatig ingevoerde subnetten (geen bekende interface, dev="")
# krijgen anders NOOIT een lokale kernel-route op de gateway zelf -- ze
# vallen dan stil terug op de standaardroute (vaak de WiFi-uplink!) en
# zijn zo onbereikbaar. Dit veroorzaakte op 23 juli een echte storing bij
# S010HMI (172.27.111.0/24 werd handmatig toegevoegd, kreeg nooit een
# lokale route, en verkeer ernaartoe verdween in het niets via wlan0).
# Aanname: een handmatig subnet zit op hetzelfde fysieke segment als het
# eerste automatisch gedetecteerde subnet -- redelijk voor platte
# industriële netwerken met meerdere bereiken op één kabel, en bevestigd
# door het S010HMI-incident zelf.
primary_iface_for_manual = next((dev for _, dev in subnet_pairs if dev), None)
if primary_iface_for_manual:
    manual_routes = [net for net, dev in subnet_pairs if not dev]
    for net in manual_routes:
        run(f"ip route replace {net} dev {primary_iface_for_manual}", check=False)
        print(f"Lokale route toegevoegd voor handmatig subnet {net} via {primary_iface_for_manual}")

    if manual_routes:
        route_cmds = "\n".join(f"ExecStart=/sbin/ip route replace {n} dev {primary_iface_for_manual}" for n in manual_routes)
        Path("/etc/systemd/system/rp-manual-routes.service").write_text(f"""[Unit]
Description=Remote Platform: herstel handmatig toegevoegde subnet-routes na opstarten
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
{route_cmds}

[Install]
WantedBy=multi-user.target
""")
        run("systemctl daemon-reload", check=False)
        run("systemctl enable rp-manual-routes.service", check=False)
        print("rp-manual-routes.service aangemaakt -- herstelt deze route(s) automatisch na elke opstart.")

try: req("POST", PORTAL_URL+f"/gateways/{gw['id']}/vpn/generate-keys")
except Exception as e: print("Generate keys melding:", e)
time.sleep(8)

host = PORTAL_URL.replace("http://","").replace("https://","").split("/")[0].split(":")[0]
endpoint = os.environ.get("REMOTE_PLATFORM_VPN_ENDPOINT", host+":51820")
try:
    out = req("POST", PORTAL_URL+f"/gateways/{gw['id']}/vpn/bootstrap", data={
        "server_code": "primary",
        "endpoint": endpoint,
        "transport_mode": "udp",
        "machine_subnets": subnets,
        "lan_interfaces": lan_interfaces
    })
    print(json.dumps(out, indent=2))
except Exception as e:
    print("VPN bootstrap melding:", e)

print("\nAgent status:")
subprocess.call("systemctl status remote-platform-agent.service --no-pager -l || true", shell=True)
print("\nKlaar.")
print("Gateway ID:", gw["id"])
print("Serial:", gw["serial"])
END_RP_INSTALLER

REMOTE_PLATFORM_URL="${DEFAULT_PORTAL_URL}" python3 /tmp/rp_installer.py
