#!/usr/bin/env python3
"""Tiny request-intake service. Stdlib only. Listens on loopback; nginx fronts it."""
import json, os, re, time, fcntl, html
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

QUEUE = "/home/agent/work/site/queue.jsonl"
RATE  = {}                     # ip -> [timestamps]
MAX_BODY   = 8192              # bytes
WINDOW, LIMIT = 3600, 5        # 5 submissions per IP per hour


def rate_ok(ip: str) -> bool:
    now = time.time()
    hits = [t for t in RATE.get(ip, []) if now - t < WINDOW]
    RATE[ip] = hits
    if len(hits) >= LIMIT:
        return False
    hits.append(now)
    return True


def append(rec: dict) -> None:
    with open(QUEUE, "a") as fh:
        fcntl.flock(fh, fcntl.LOCK_EX)
        fh.write(json.dumps(rec) + "\n")
        fcntl.flock(fh, fcntl.LOCK_UN)


class H(BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    server_version = "aaw"

    def _send(self, code: int, body: bytes, ctype="application/json"):
        self.send_response(code)
        self.send_header("Content-Type", ctype)
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, *a):                      # keep nginx as the access log
        pass

    def do_POST(self):
        if self.path != "/api/request":
            return self._send(404, b'{"error":"not found"}')

        # real client IP comes from nginx
        ip = self.headers.get("X-Real-IP", self.client_address[0])
        if not rate_ok(ip):
            return self._send(429, b'{"error":"rate limited - try again later"}')

        try:
            n = int(self.headers.get("Content-Length", 0))
        except ValueError:
            return self._send(400, b'{"error":"bad length"}')
        if n <= 0 or n > MAX_BODY:
            return self._send(413, b'{"error":"too large"}')

        try:
            data = json.loads(self.rfile.read(n))
            task = str(data.get("task", "")).strip()
            contact = str(data.get("contact", "")).strip()
        except Exception:
            return self._send(400, b'{"error":"bad json"}')

        if not (10 <= len(task) <= 4000):
            return self._send(400, b'{"error":"task must be 10-4000 chars"}')
        if not (3 <= len(contact) <= 200):
            return self._send(400, b'{"error":"contact required"}')

        append({
            "ts": int(time.time()),
            "iso": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
            "ip": ip,
            "ua": self.headers.get("User-Agent", "")[:200],
            "contact": html.escape(contact)[:200],
            "task": task[:4000],
            "status": "new",
        })
        return self._send(200, b'{"ok":true}')


if __name__ == "__main__":
    os.makedirs(os.path.dirname(QUEUE), exist_ok=True)
    ThreadingHTTPServer(("127.0.0.1", 8080), H).serve_forever()
