import os
import subprocess
import re
import time
import unicodedata
from tempfile import NamedTemporaryFile
from flask import Flask, render_template, request, redirect, url_for, flash, session, abort

# ================== Flask basis ==================
app = Flask(__name__)
app.secret_key = 'easycomp_secret'  # voor flash + session

# Dev / cache-busting
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0  # static niet cachen in dev
app.config['ETAG_DISABLED'] = True

@app.after_request
def add_no_cache_headers(resp):
    # Agressieve no-cache (helpt tegen Safari die-hard cache)
    resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
    resp.headers['Pragma'] = 'no-cache'
    resp.headers['Expires'] = '0'
    return resp

@app.context_processor
def inject_build_version():
    # Versiestempel voor cache-busting in templates
    return {'BUILD_TS': str(int(time.time()))}

# ================== Paden / constanten ==================
CONFIG_PATH = "/home/ecz-gebruiker/BirdNET-Pi/birdnet.conf"
HOSTS_PATH = "/etc/hosts"
LOGO_URL = "/static/media/images/ECZ-LOGO-WHITE.png"
DEFAULT_FLICKR_KEY = "8427550a6678894dfe644455025bcf3a"
HOSTNAME_PREFIX = "ECZ-BIRDNET-PI-"
SITE_PREFIX = "ECZ-BirdNET-PI-WatchHouse"

# ================== Root check ==================
def ensure_root():
    if os.geteuid() != 0:
        # Service hoort als root te draaien (systemd User=root of via root unit)
        abort(500, description="Deze service moet als root draaien (sudo/systemd).")

# ================== Helpers ==================
def parse_config(path: str) -> dict:
    """Lees KEY=VALUE configbestand in dict."""
    cfg = {}
    try:
        with open(path, "r") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#"):
                    continue
                if "=" in line:
                    k, v = line.split("=", 1)
                    cfg[k.strip()] = v.strip()
    except FileNotFoundError:
        pass
    return cfg

def atomic_write(path: str, content: str):
    """Schrijf atomisch naar bestand om partial writes te voorkomen."""
    dirn = os.path.dirname(path) or "."
    with NamedTemporaryFile("w", delete=False, dir=dirn) as tf:
        tf.write(content)
        tmpname = tf.name
    os.replace(tmpname, path)

def update_config(path: str, updates: dict):
    """Update configbestand met KEY=VALUE (stabiele sort)."""
    cfg = parse_config(path)
    cfg.update({k: str(v) for k, v in updates.items() if v is not None})
    lines = [f"{k}={v}\n" for k, v in sorted(cfg.items())]
    atomic_write(path, "".join(lines))

def slugify(value: str) -> str:
    """Maak nette slug: ASCII, spaties -> streepjes, alleen [A-Za-z0-9-]."""
    if not value:
        return ""
    value = unicodedata.normalize("NFKD", value)
    value = "".join(c for c in value if not unicodedata.combining(c))
    value = re.sub(r"[^A-Za-z0-9\- ]+", "", value)
    value = re.sub(r"\s+", "-", value.strip())
    return value

def build_site_name(location_name: str, suffix: str) -> str:
    parts = [SITE_PREFIX]
    if location_name:
        parts.append(slugify(location_name))
    if suffix:
        parts.append(slugify(suffix))
    return "-".join(parts)

def set_hostname_and_hosts(new_hostname: str):
    """Zet systeemhostnaam en zorg dat /etc/hosts 127.0.1.1 -> nieuwe host bevat."""
    # hostname zetten
    subprocess.run(["hostnamectl", "set-hostname", new_hostname], check=True)

    # hosts bewerken
    try:
        with open(HOSTS_PATH, "r") as f:
            hosts = f.read()
    except FileNotFoundError:
        hosts = ""

    # Regelvorm: 127.0.1.1 <hostname>   (meestal op Debian)
    pattern = r"^127\.0\.1\.1\s+\S+.*$"
    replacement = f"127.0.1.1\t{new_hostname}"
    if re.search(pattern, hosts, flags=re.M):
        hosts = re.sub(pattern, replacement, hosts, flags=re.M)
    else:
        if not hosts.endswith("\n"):
            hosts += "\n"
        hosts += replacement + "\n"

    atomic_write(HOSTS_PATH, hosts)

def connect_wifi(ssid: str, wifi_pass: str):
    """Maak/activeer tijdelijke nmcli-verbinding voor Wi‑Fi."""
    # Verwijder bestaande testverbinding (best effort)
    subprocess.run(["nmcli", "connection", "delete", "client-wifi-setup"], check=False)

    # Aanmaken + beveiligen
    subprocess.run([
        "nmcli", "connection", "add", "type", "wifi", "con-name", "client-wifi-setup",
        "ifname", "wlan0", "ssid", ssid
    ], check=True)
    subprocess.run([
        "nmcli", "connection", "modify", "client-wifi-setup",
        "wifi-sec.key-mgmt", "wpa-psk",
        "wifi-sec.psk", wifi_pass
    ], check=True)
    # Verbinden
    subprocess.run(["nmcli", "connection", "up", "client-wifi-setup"], check=True)

# ---- Validators voor stap 3 ----
def is_http_url(u: str) -> bool:
    return bool(u and re.match(r'^https?://.+', u.strip(), re.I))

def is_rtsp_url(u: str) -> bool:
    return bool(u and re.match(r'^rtsp://.+', u.strip(), re.I))

def is_birdweather_id(v: str) -> bool:
    # Toegestaan: letters/cijfers/streepje, vaak 'BW-' prefix, minimaal 6 tekens totaal
    if not v:
        return True  # leeg = toegestaan (optioneel)
    if len(v) < 6:
        return False
    return bool(re.match(r'^[A-Za-z0-9\-]+$', v))

# ================== Routes ==================
@app.route('/', methods=['GET', 'POST'])
def setup():
    ensure_root()

    if 'step' not in session:
        session['step'] = 0
        session['data'] = {}

    # Pre-provisioning via URL: ?bw=...&lock_bw=1
    if request.method == 'GET':
        data = session.get('data', {})
        bw_arg = (request.args.get('bw') or "").strip()
        lock_bw_arg = (request.args.get('lock_bw') or "").strip()
        if bw_arg:
            data['birdweather_id'] = bw_arg
        if lock_bw_arg.lower() in ('1', 'true', 'yes', 'on'):
            data['lock_birdweather'] = True
        session['data'] = data

    # Step bepaling
    if request.method == 'GET' and request.args.get('step') is not None:
        try:
            step = int(request.args.get('step'))
        except ValueError:
            step = session.get('step', 0)
        session['step'] = step
    else:
        try:
            step = int(request.form.get('step', session.get('step', 0)))
        except (TypeError, ValueError):
            step = session.get('step', 0)

    data = session.get('data', {})

    # ================== POST handlers ==================
    if request.method == 'POST':
        # TERUG?
        if 'back' in request.form:
            step = max(1, step - 1)  # nooit terug naar 0 (intro)
            session['step'] = step
            session['data'] = data
            return redirect(url_for('setup'))

        # VOLGENDE
        if step == 1:
            # Keuze verbinding
            data['connection_type'] = request.form.get('connection_type', 'wifi').strip()  # 'wifi' of 'ethernet'

            # Wi‑Fi velden
            data['ssid'] = request.form.get('ssid', '').strip()
            data['wifi_password'] = request.form.get('wifi_password', '').strip()
            data['wifi_ip_mode'] = request.form.get('wifi_ip_mode', 'dhcp').strip()  # 'dhcp' of 'static'
            data['wifi_ip'] = request.form.get('wifi_ip', '').strip()
            data['wifi_gw'] = request.form.get('wifi_gw', '').strip()
            data['wifi_dns'] = request.form.get('wifi_dns', '').strip()

            # Ethernet velden
            data['eth_ip_mode'] = request.form.get('eth_ip_mode', 'dhcp').strip()
            data['eth_ip'] = request.form.get('eth_ip', '').strip()
            data['eth_gw'] = request.form.get('eth_gw', '').strip()
            data['eth_dns'] = request.form.get('eth_dns', '').strip()

            # Validatie per keuze
            if data['connection_type'] == 'wifi':
                if not data['ssid'] or not data['wifi_password'] or len(data['wifi_password']) < 8:
                    flash("Vul een geldige Wi‑Fi naam en wachtwoord in (minimaal 8 tekens).")
                else:
                    if data['wifi_ip_mode'] == 'static' and (not data['wifi_ip'] or not data['wifi_gw'] or not data['wifi_dns']):
                        flash("Vul bij Vast IP (Wi‑Fi) ook IP‑adres, gateway en DNS in.")
                    else:
                        step = 2
            else:
                # Ethernet
                if data['eth_ip_mode'] == 'static' and (not data['eth_ip'] or not data['eth_gw'] or not data['eth_dns']):
                    flash("Vul bij Vast IP (Ethernet) ook IP‑adres, gateway en DNS in.")
                else:
                    step = 2

        elif step == 2:
            # Stap 2: hostnaam-suffix, automatische site_name, locatie & adres
            data['hostname'] = request.form.get('hostname', '').strip()
            data['site_name'] = request.form.get('site_name', '').strip()  # readonly client-side
            data['latitude'] = request.form.get('latitude', '').strip()
            data['longitude'] = request.form.get('longitude', '').strip()

            # Adresvelden (optioneel)
            data['street']    = request.form.get('street', '').strip()
            data['houseno']   = request.form.get('houseno', '').strip()
            data['postcode']  = request.form.get('postcode', '').strip()
            data['city']      = request.form.get('city', '').strip()
            data['province']  = request.form.get('province', '').strip()
            data['location_name'] = request.form.get('location_name', '').strip()

            # Validatie hostnaam-suffix
            if not data['hostname']:
                flash("Vul een hostnaam‑suffix in.")
            elif not re.fullmatch(r'[A-Za-z0-9\-]+', data['hostname']):
                flash("Hostnaam‑suffix mag alleen letters, cijfers en koppeltekens bevatten.")
            else:
                # site_name fallback (server-side), voor het geval JS niet liep
                if not data['site_name']:
                    data['site_name'] = build_site_name(data.get('location_name',''), data['hostname'])

                # Coördinaten check
                if data['latitude'] and data['longitude']:
                    try:
                        float(data['latitude']); float(data['longitude'])
                    except ValueError:
                        flash("Ongeldige coördinaten. Gebruik bijv. 52.3984 en 4.9206.")
                        session['step'] = step
                        session['data'] = data
                        return redirect(url_for('setup'))

                # Hostnaam definitief opbouwen + lengte‑check (label max 63)
                final = f"{HOSTNAME_PREFIX}{slugify(data['hostname'])}"
                if len(final) > 63:
                    max_suffix_len = 63 - len(HOSTNAME_PREFIX)
                    flash(f"Hostnaam te lang. Kies een kortere suffix (max {max_suffix_len} tekens).")
                    session['step'] = step
                    session['data'] = data
                    return redirect(url_for('setup'))

                data['hostname_final'] = final
                step = 3  # door naar volgende stap

        elif step == 3:
            # Extra opties (met validatie)
            data['birdweather_id'] = request.form.get('birdweather_id', '').strip()
            data['caddy_pwd'] = request.form.get('caddy_pwd', '').strip()
            data['ice_pwd'] = request.form.get('ice_pwd', '').strip()
            data['birdnetpi_url'] = request.form.get('birdnetpi_url', '').strip()
            data['rtsp_stream'] = request.form.get('rtsp_stream', '').strip()
            data['flickr_api_key'] = request.form.get('flickr_api_key', '').strip() or DEFAULT_FLICKR_KEY

            errors = []
            if data['birdnetpi_url'] and not is_http_url(data['birdnetpi_url']):
                errors.append("De URL van je vogelpagina moet beginnen met http:// of https://")
            if data['rtsp_stream'] and not is_rtsp_url(data['rtsp_stream']):
                errors.append("Het camera‑adres (RTSP) moet beginnen met rtsp://")
            if data['caddy_pwd'] and len(data['caddy_pwd']) < 6:
                errors.append("Kies een sterker wachtwoord voor de vogelpagina (minstens 6 tekens).")
            if not is_birdweather_id(data['birdweather_id']):
                errors.append("BirdWeather code lijkt ongeldig. Gebruik letters/cijfers en streepjes (bijv. BW-1234-ABCD).")

            if errors:
                for e in errors:
                    flash(e)
            else:
                step = 4

        elif step == 4:
            # Afronden: toepassen & config wegschrijven
            try:
                ssid = data.get('ssid', '')
                wifi_pass = data.get('wifi_password', '')
                host_suffix = data.get('hostname', '')
                new_hostname = data.get('hostname_final') or (f"{HOSTNAME_PREFIX}{slugify(host_suffix)}" if host_suffix else "")
                site_name = data.get('site_name', '')
                birdweather = data.get('birdweather_id', '')
                caddy_pwd = data.get('caddy_pwd', '')
                lat = data.get('latitude', '')
                lon = data.get('longitude', '')
                ice_pwd = data.get('ice_pwd', '')
                birdnetpi_url = data.get('birdnetpi_url', '')
                rtsp_stream = data.get('rtsp_stream', '')
                flickr_api_key = data.get('flickr_api_key', '') or DEFAULT_FLICKR_KEY

                # Alleen wifi nu verbinden; ethernet regelen we later (optioneel)
                if ssid and wifi_pass and data.get('connection_type') == 'wifi':
                    try:
                        connect_wifi(ssid, wifi_pass)
                    except subprocess.CalledProcessError as e:
                        flash(f"Wi‑Fi verbinden mislukt: {e}")
                        session['step'] = step
                        session['data'] = data
                        return redirect(url_for('setup'))

                # Hostname toepassen
                if new_hostname:
                    try:
                        set_hostname_and_hosts(new_hostname)
                    except subprocess.CalledProcessError as e:
                        flash(f"Hostnaam aanpassen mislukt: {e}")
                        session['step'] = step
                        session['data'] = data
                        return redirect(url_for('setup'))
                    except OSError as e:
                        flash(f"Kon /etc/hosts niet bijwerken: {e}")
                        session['step'] = step
                        session['data'] = data
                        return redirect(url_for('setup'))

                updates = {
                    "SITE_NAME": site_name,
                    "LATITUDE": lat,
                    "LONGITUDE": lon,
                    "BIRDWEATHER_ID": birdweather,
                    "CADDY_PWD": caddy_pwd,
                    "ICE_PWD": ice_pwd,
                    "BIRDNETPI_URL": birdnetpi_url,
                    "RTSP_STREAM": rtsp_stream,
                    "FLICKR_API_KEY": flickr_api_key,

                    # Netwerkkeuzes bewaren
                    "NET_TYPE": data.get('connection_type', ''),
                    "WIFI_IP_MODE": data.get('wifi_ip_mode', ''),
                    "WIFI_IP": data.get('wifi_ip', ''),
                    "WIFI_GW": data.get('wifi_gw', ''),
                    "WIFI_DNS": data.get('wifi_dns', ''),
                    "ETH_IP_MODE": data.get('eth_ip_mode', ''),
                    "ETH_IP": data.get('eth_ip', ''),
                    "ETH_GW": data.get('eth_gw', ''),
                    "ETH_DNS": data.get('eth_dns', ''),

                    # Adres handig om te hebben
                    "STREET": data.get('street',''),
                    "HOUSENO": data.get('houseno',''),
                    "POSTCODE": data.get('postcode',''),
                    "CITY": data.get('city',''),
                    "PROVINCE": data.get('province',''),
                    "LOCATION_NAME": data.get('location_name',''),
                }
                update_config(CONFIG_PATH, updates)
                session.clear()
                return redirect(url_for('done'))
            except Exception as e:
                flash(f"Fout bij opslaan: {e}")

        # Bewaar state en ga door
        session['step'] = step
        session['data'] = data
        return redirect(url_for('setup'))

    # ================== GET render ==================
    return render_template(
        'wizard.html',
        logo=LOGO_URL,
        step=int(session.get('step', 0) or 0),
        data=session.get('data', {}) or {}
    )

@app.route('/done')
def done():
    ensure_root()
    # Reboot op de achtergrond na 3 sec (zacht)
    os.system("sleep 3 && systemctl reboot &")
    return "<h2>Installatie voltooid! Je mag dit scherm sluiten.</h2>"

# ================== Entrypoint ==================
if __name__ == "__main__":
    # In dev: run flask server. In productie: via systemd (WSGI desnoods).
    app.run(host='0.0.0.0', port=1234, debug=True)
