#!/usr/bin/env python3
"""
raspberry_pi_wake_server.py — tap-to-wake page for the WIVEXX PC  (v1.9)
═══════════════════════════════════════════════════════════════════════════
Run this on the Raspberry Pi (stdlib only, no packages):

    python3 raspberry_pi_wake_server.py

Then from your phone open:   http://<pi-ip>:8080
Enter the PIN once, tap the big button → magic packet → PC boots → WIVEXX
autostarts → open http://your-pc-name:7777 as usual.

Auto-start it on the Pi (so it's always available):
    sudo tee /etc/systemd/system/wivexx-wake.service > /dev/null <<'EOF'
    [Unit]
    Description=WIVEXX wake server
    After=network-online.target
    [Service]
    ExecStart=/usr/bin/python3 /home/pi/raspberry_pi_wake_server.py
    Restart=always
    [Install]
    WantedBy=multi-user.target
    EOF
    sudo systemctl enable --now wivexx-wake

REMOTE ACCESS (outside the house) — the safe way only:
    Install Tailscale on the Pi AND your phone (free): then this page works
    from anywhere at http://<pi-tailscale-ip>:8080, fully encrypted, with
    ZERO ports exposed to the internet. NEVER port-forward this server or
    UDP 9 on the router — magic packets are unauthenticated and this page
    can power on your PC.
"""

import json
import re
import socket
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

# ── WIVEXX PC (PCDURITAS) — verified WOL-capable incl. wake-from-S5 ───────────
PC_MAC        = "50:EB:F6:80:1C:C0"
PC_HOST       = "PCDURITAS"
LAN_BROADCAST = "192.168.0.255"
WOL_PORT      = 9
SERVER_PORT   = 8080
PIN           = "8948"                  # same PIN as the WIVEXX website


def send_magic_packet(mac: str = PC_MAC, broadcast: str = LAN_BROADCAST,
                      port: int = WOL_PORT, repeats: int = 5) -> None:
    clean = re.sub(r"[^0-9a-fA-F]", "", mac)
    payload = b"\xff" * 6 + bytes.fromhex(clean) * 16
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        for _ in range(repeats):
            s.sendto(payload, (broadcast, port))


def pc_is_awake(host: str = PC_HOST, port: int = 7777) -> bool:
    """True if the WIVEXX web port answers (i.e. the PC is already on)."""
    try:
        with socket.create_connection((host, port), timeout=2):
            return True
    except OSError:
        return False


_PAGE = """<!DOCTYPE html>
<html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Wake WIVEXX PC</title>
<style>
  body { background:#040e14; color:#cfeee4; font-family:Consolas,monospace;
         height:100dvh; margin:0; display:flex; flex-direction:column;
         align-items:center; justify-content:center; gap:22px; }
  h1 { color:#00ffcc; letter-spacing:4px; font-size:20px; margin:0; }
  #status { color:#7ecfbb; font-size:13px; min-height:18px; }
  button#wake { width:190px; height:190px; border-radius:50%;
         background:#0a1c28; color:#00ffcc; border:3px solid #00ffcc;
         font:inherit; font-size:20px; box-shadow:0 0 30px #00ffcc44; }
  button#wake:active { background:#00ffcc; color:#040e14; }
  input { background:#0a1c28; border:1px solid #0d3344; color:#cfeee4;
          border-radius:8px; padding:10px; text-align:center; font-size:18px;
          letter-spacing:5px; width:160px; }
  a { color:#446688; font-size:12px; }
</style></head><body>
<h1>Wivexx</h1>
<input id="pin" type="password" placeholder="PIN" inputmode="numeric">
<button id="wake" onclick="wake()">⚡ WAKE PC</button>
<div id="status"></div>
<a href="http://your-pc-name:7777">open WIVEXX (once the PC is up)</a>
<script>
const st = document.getElementById('status');
document.getElementById('pin').value = localStorage.getItem('wake_pin') || '';
async function wake() {
  const pin = document.getElementById('pin').value.trim();
  localStorage.setItem('wake_pin', pin);
  st.textContent = 'sending wake signal…';
  try {
    const r = await fetch('/wake', {method:'POST',
      headers:{'Content-Type':'application/json'},
      body: JSON.stringify({pin: pin})});
    const d = await r.json();
    if (!d.ok) { st.textContent = d.error || 'wrong PIN'; return; }
    st.textContent = 'Magic packet sent ⚡ — checking…';
    let tries = 0;
    const t = setInterval(async () => {
      tries++;
      const c = await fetch('/check').then(r => r.json()).catch(() => ({up:false}));
      if (c.up) { clearInterval(t); st.textContent = '✅ PC is awake — WIVEXX is up!'; }
      else if (tries > 40) { clearInterval(t); st.textContent = 'no answer yet — give it a minute'; }
      else st.textContent = 'waiting for the PC to boot… (' + tries*3 + 's)';
    }, 3000);
  } catch (e) { st.textContent = 'error sending wake signal'; }
}
</script></body></html>"""


class _H(BaseHTTPRequestHandler):
    def _json(self, code, obj):
        body = json.dumps(obj).encode()
        self.send_response(code)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, *a):
        pass

    def do_GET(self):
        if self.path == "/check":
            self._json(200, {"up": pc_is_awake()})
            return
        body = _PAGE.encode()
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_POST(self):
        if self.path != "/wake":
            self._json(404, {"ok": False})
            return
        try:
            n = int(self.headers.get("Content-Length", 0))
            data = json.loads(self.rfile.read(min(n, 1000)))
        except Exception:
            data = {}
        if str(data.get("pin", "")).strip() != PIN:
            self._json(401, {"ok": False, "error": "wrong PIN"})
            return
        try:
            send_magic_packet()
            print(f"wake packet sent for {PC_MAC}")
            self._json(200, {"ok": True})
        except Exception as e:
            self._json(500, {"ok": False, "error": str(e)})


if __name__ == "__main__":
    print(f"WIVEXX wake server on http://0.0.0.0:{SERVER_PORT}  "
          f"(target {PC_HOST} / {PC_MAC})")
    ThreadingHTTPServer(("0.0.0.0", SERVER_PORT), _H).serve_forever()
