#!/usr/bin/env python
r"""
grounding_receiver.py — runs on the 5090. Lets Calypso push grounding.md
straight into C:\malin hands-free, so Jun never hand-copies it again.

Security (this matters — the 5090 is on a shared tailnet owned by a different account):
  - Binds to the Tailscale interface and ONLY accepts writes from Calypso's Mac
    (feral-box-jun, tailnet IP 100.105.155.1). Every other source is rejected + logged.
  - Only ever writes ONE file: C:\malin\grounding.md. It cannot write anywhere else.
  - Writes atomically (temp file + replace) so a half-sent file never corrupts her memory.
  - Logs every request (accepted or rejected) to C:\malin\grounding_receiver.log.

Run on the 5090 (leave it running, like network_watch):
    py grounding_receiver.py
Ctrl+C to stop.
"""
import http.server, os, shutil, socketserver, tempfile, time

PORT       = 1235
ALLOWED_IP = "100.105.155.1"                 # Calypso's Mac (feral-box-jun). Only this host may write.
TARGET     = r"C:\malin\grounding.md"
LOGFILE    = r"C:\malin\grounding_receiver.log"
MAX_BYTES  = 256 * 1024                       # grounding is small; cap to be safe


def log(line):
    stamp = time.strftime("%Y-%m-%d %H:%M:%S")
    msg = f"[{stamp}] {line}"
    try:
        with open(LOGFILE, "a", encoding="utf-8") as f:
            f.write(msg + "\n")
    except Exception:
        pass
    print(msg)


class Handler(http.server.BaseHTTPRequestHandler):
    def _client_ip(self):
        # Tailscale connections preserve the real peer IP
        return self.client_address[0]

    def do_GET(self):
        if self.path == "/health":
            self.send_response(200); self.end_headers()
            self.wfile.write(b"grounding_receiver ok")
        else:
            self.send_response(404); self.end_headers()

    def do_POST(self):
        ip = self._client_ip()
        if self.path != "/grounding":
            self.send_response(404); self.end_headers(); return
        if ip != ALLOWED_IP:
            log(f"REJECTED write from {ip} (not Calypso) -> 403")
            self.send_response(403); self.end_headers()
            self.wfile.write(b"forbidden"); return
        length = int(self.headers.get("Content-Length", 0))
        if length <= 0 or length > MAX_BYTES:
            log(f"REJECTED from {ip}: bad length {length}")
            self.send_response(400); self.end_headers(); return
        body = self.rfile.read(length)
        try:
            os.makedirs(os.path.dirname(TARGET), exist_ok=True)
            fd, tmp = tempfile.mkstemp(dir=os.path.dirname(TARGET), suffix=".tmp")
            with os.fdopen(fd, "wb") as f:
                f.write(body)
            shutil.move(tmp, TARGET)
            log(f"OK wrote grounding.md from {ip} ({length} bytes)")
            self.send_response(200); self.end_headers()
            self.wfile.write(b"grounding updated")
        except Exception as e:
            log(f"ERROR writing grounding from {ip}: {e}")
            self.send_response(500); self.end_headers()

    def log_message(self, *a):
        pass  # we do our own logging


class Server(socketserver.ThreadingMixIn, http.server.HTTPServer):
    daemon_threads = True


if __name__ == "__main__":
    log(f"grounding_receiver starting on port {PORT}; only {ALLOWED_IP} may write -> {TARGET}")
    Server(("0.0.0.0", PORT), Handler).serve_forever()
