#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 Anil Jangra
"""
Broker desk: a reference integration of RiskRouter, in one file.

    RISKROUTER_API_KEY=rr_sandbox_... python3 app.py      # then open http://127.0.0.1:8900
    (take a key at https://riskrouter.eu/integrate#get-a-key)

What it shows a broker-software vendor, end to end:

  1. The broker configures cover; the server prices it (the app never does).
  2. The quote is recorded in the v1 ledger with an idempotency key derived
     from the app's own quote id, so a double-click or a retry never records
     twice.
  3. The broker's demands-and-needs note (IDD Article 20), which names the
     customer, stays in THIS app's data file. Only a salted SHA-256 of it goes
     to the v2 evidence log.
  4. "Evidence pack" bundles, for one customer: the note, its salt, the
     single-entry proof of the quote and the Merkle proof of the note. Anyone
     can check the pack offline with the public verifiers, and learns nothing
     about any other customer.

Standard library only. Data lives in ./broker-desk-data.json. Not for
production: no authentication, no concurrency control, no deletion workflow.
"""
import html
import json
import os
import secrets
import sys
import urllib.parse
import uuid
from http.server import BaseHTTPRequestHandler, HTTPServer

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "python"))
from riskrouter import RiskRouter, RiskRouterError  # noqa: E402

DATA = os.environ.get("BROKER_DESK_DATA", os.path.join(HERE, "broker-desk-data.json"))
rr = RiskRouter()


def load():
    if not os.path.exists(DATA):
        return {"quotes": []}
    with open(DATA) as fh:
        return json.load(fh)


def save(db):
    with open(DATA, "w") as fh:
        json.dump(db, fh, indent=2)


def make_entry_proof(export, chain_index):
    """The v1 single-entry proof, cut from an export (same format as tools/entry-proof.mjs)."""
    entry = next(e for e in export["entries"] if int(e["chain_index"]) == int(chain_index))
    path = [{k: link[k] for k in ("chain_index", "row_hash", "prev_hash")}
            for link in sorted(export["skeleton"], key=lambda r: int(r["chain_index"])) if int(link["chain_index"]) >= int(chain_index)]
    proof = {"format": "riskrouter-entry-proof|v1", "entry": entry, "path": path, "attestation": export.get("attestation")}
    if export.get("signature"):
        proof["signature"] = export["signature"]
    return proof


def page(body):
    return ("<!doctype html><meta charset=utf-8><title>Broker desk</title>"
            "<style>body{font:15px system-ui;max-width:900px;margin:32px auto;padding:0 16px}"
            "table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid #ddd;padding:6px;text-align:left}"
            "textarea{width:100%;height:80px}.note{color:#555;font-size:13px}</style>"
            "<h1>Broker desk <small class=note>RiskRouter reference integration</small></h1>" + body)


class Desk(BaseHTTPRequestHandler):
    def send(self, status, body, ctype="text/html; charset=utf-8", extra=None):
        data = body.encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", ctype)
        self.send_header("Content-Length", str(len(data)))
        for k, v in (extra or {}).items():
            self.send_header(k, v)
        self.end_headers()
        self.wfile.write(data)

    def log_message(self, *args):
        pass

    def do_GET(self):
        url = urllib.parse.urlparse(self.path)
        if url.path == "/pack":
            return self.evidence_pack(urllib.parse.parse_qs(url.query).get("id", [""])[0])
        db = load()
        rows = "".join(
            "<tr><td>%s</td><td>%s</td><td>EUR %s</td><td>#%s</td><td>leaf %s</td><td><a href='/pack?id=%s'>evidence pack</a></td></tr>"
            % (html.escape(q["customer"]), html.escape(q["vertical"]), q["premium"], q["chain_index"], q["evidence_leaf"], q["id"])
            for q in db["quotes"])
        self.send(200, page(
            "<form method=post action=/quote>"
            "<p><label>Customer <input name=customer required></label> "
            "<label>Vertical <select name=vertical><option>mobility<option>digital_nomad<option>urban_rental<option>micro_travel</select></label></p>"
            "<p><label>Options (comma separated codes) <input name=components value='commuter_roadside' size=50></label></p>"
            "<p><label>Demands and needs (stays in this app; only its fingerprint is recorded)<textarea name=note required></textarea></label></p>"
            "<button>Price and record</button></form>"
            "<h2>Quotes</h2><table><tr><th>Customer</th><th>Vertical</th><th>Premium</th><th>Ledger</th><th>Evidence</th><th></th></tr>%s</table>"
            % rows))

    def do_POST(self):
        length = int(self.headers.get("Content-Length", 0))
        form = urllib.parse.parse_qs(self.rfile.read(length).decode("utf-8"))
        get = lambda k: (form.get(k) or [""])[0].strip()
        local_id = str(uuid.uuid4())
        components = {c.strip(): True for c in get("components").split(",") if c.strip()}
        try:
            # The app's own quote id is the idempotency key: a retry can never record twice.
            quote = rr.record_quote(get("vertical"), components, idempotency_key="desk-" + local_id)
            if not quote["recorded"]:
                return self.send(503, page("<p>Priced, but the ledger did not record it. Nothing is claimed. <a href=/>Back</a></p>"))
            salt = secrets.token_bytes(16)
            evidence = rr.record_evidence(get("note").encode("utf-8"), "idd.demands-needs", salt=salt)
        except RiskRouterError as error:
            return self.send(error.status, page("<p>%s <a href=/>Back</a></p>" % html.escape(str(error))))
        db = load()
        db["quotes"].append({
            "id": local_id, "customer": get("customer"), "vertical": get("vertical"), "note": get("note"),
            "salt_hex": salt.hex(), "premium": quote["quote"]["totals"]["total_monthly_premium"],
            "chain_index": quote["persistence"]["seal"]["chain_index"], "evidence_leaf": evidence["entry"]["leaf_index"],
            "evidence_entry": evidence["entry"]})
        save(db)
        self.send(303, "", extra={"Location": "/"})

    def evidence_pack(self, local_id):
        q = next((q for q in load()["quotes"] if q["id"] == local_id), None)
        if not q:
            return self.send(404, page("<p>No such quote.</p>"))
        pack = {
            "format": "broker-desk-evidence-pack|1",
            "customer": q["customer"],
            "demands_and_needs": {"note": q["note"], "salt_hex": q["salt_hex"],
                                  "how_to_check": "SHA-256(salt || note) must equal evidence_proof.entry.record_digest"},
            "quote_proof": make_entry_proof(rr.export(), q["chain_index"]),
            "evidence_proof": rr.evidence_proof(q["evidence_leaf"]),
        }
        self.send(200, json.dumps(pack, indent=2), "application/json",
                  {"Content-Disposition": "attachment; filename=evidence-pack-%s.json" % local_id[:8]})


if __name__ == "__main__":
    port = int(os.environ.get("BROKER_DESK_PORT", "8900"))
    print("Broker desk on http://127.0.0.1:%d  (RiskRouter at %s)" % (port, rr.base_url))
    HTTPServer(("127.0.0.1", port), Desk).serve_forever()
