"""HTTP edge for the agent. Policy lives in dispatch(), not in the socket.

This is a teaching server. It is enough to see auth, body size, and the agent
loop on one port. It is not a production gateway (no TLS termination, no
multi-worker rate limit).
"""

from __future__ import annotations

import json
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

from northline.loop import run_agent
from northline.orders import lookup_order
from northline.scripted import order_agent
from northline.trace import Tracer

MAX_BODY = 4096


def dispatch(method, path, headers, body, *, token, complete=order_agent, tools=None, max_body=MAX_BODY, admit=True):
    headers = {k.lower(): v for k, v in dict(headers).items()}
    if method == "GET" and path == "/health":
        return 200, {"ok": True}
    if method != "POST" or path != "/v1/tasks":
        return 404, {"error": "not_found"}
    if not token:
        return 500, {"error": "server_token_unset"}
    auth = headers.get("authorization", "")
    if auth != f"Bearer {token}":
        return 401, {"error": "unauthorized"}
    raw = body if isinstance(body, (bytes, bytearray)) else (body or "").encode()
    if len(raw) > max_body:
        return 413, {"error": "body_too_large"}
    try:
        payload = json.loads(raw.decode() or "{}")
    except json.JSONDecodeError:
        return 400, {"error": "invalid_json"}
    if not isinstance(payload, dict) or not isinstance(payload.get("task"), str):
        return 400, {"error": "task_required"}
    if len(payload["task"]) > 1000:
        return 400, {"error": "task_too_long"}
    # Budget and rate limit shed load before any model call.
    if not admit:
        return 429, {"error": "rate_limited"}
    tools = tools if tools is not None else {"lookup_order": lookup_order}
    tracer = Tracer([])
    result = run_agent(payload["task"], complete, tools, trace=tracer)
    # Do not echo the full transcript by default. It can contain tool payloads.
    # trace_id is the join key for a complaint. It is not a secret.
    return 200, {
        "final": result["final"],
        "error": result["error"],
        "steps": result["steps"],
        "trace_id": tracer.trace_id,
    }


class Handler(BaseHTTPRequestHandler):
    def _write(self, status, payload):
        data = json.dumps(payload).encode()
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        self.wfile.write(data)

    def _go(self):
        length = int(self.headers.get("Content-Length", "0") or 0)
        # Read at most MAX_BODY+1 so an oversized body cannot exhaust memory
        # before the policy check. A real proxy should also cap this.
        body = self.rfile.read(min(length, MAX_BODY + 1)) if length else b""
        status, payload = dispatch(
            self.command,
            self.path.split("?", 1)[0],
            {k: v for k, v in self.headers.items()},
            body,
            token=os.environ.get("NORTHLINE_TOKEN", ""),
        )
        self._write(status, payload)

    def do_GET(self):
        self._go()

    def do_POST(self):
        self._go()

    def log_message(self, fmt, *args):
        # Default logging writes the path. Keep it. Do not log bodies.
        super().log_message(fmt, *args)


def serve(host="127.0.0.1", port=8080):
    ThreadingHTTPServer((host, port), Handler).serve_forever()


if __name__ == "__main__":
    serve()
