"""Turn KarmaDue's OpenAPI spec into function-calling tool definitions (standard library only).

Get it:  curl -O https://karmadue.expo.app/recipes/kd_openapi_tools.py
Docs:    https://karmadue.expo.app/docs/openapi-tools.md
"""
import json, os, urllib.parse, urllib.request

SPEC_URL = "https://karmadue.expo.app/openapi.json"
KD_VIA = os.environ.get("KARMADUE_VIA", "recipe-openapi-tools")

UA = "karmadue-openapi-tools/1 (+https://karmadue.expo.app)"  # the site's CDN refuses the default Python-urllib agent

def load_spec(url=SPEC_URL):
    with urllib.request.urlopen(urllib.request.Request(url, headers={"user-agent": UA}), timeout=15) as r:
        return json.load(r)

def _resolve(spec, node):
    if isinstance(node, dict):
        if "$ref" in node:
            name = node["$ref"].split("/")[-1]
            return _resolve(spec, spec["components"]["schemas"][name])
        return {k: _resolve(spec, v) for k, v in node.items() if k != "examples"}
    if isinstance(node, list):
        return [_resolve(spec, x) for x in node]
    return node

def tools(spec, only=("preflightCheck",)):
    """OpenAI-style tool list ({"type":"function","function":{name,description,parameters}})."""
    out = []
    for path, methods in spec["paths"].items():
        for method, op in methods.items():
            if only and op["operationId"] not in only:
                continue
            props, req = {}, []
            for p in op.get("parameters", []):
                if p["in"] in ("path", "query"):
                    props[p["name"]] = _resolve(spec, p.get("schema", {"type": "string"}))
                    if p.get("required"):
                        req.append(p["name"])
            body = op.get("requestBody", {}).get("content", {}).get("application/json", {}).get("schema")
            if body:
                b = _resolve(spec, body)
                props.update(b.get("properties", {}))
                req += b.get("required", [])
            out.append({"type": "function", "function": {"name": op["operationId"],
                        "description": op.get("description") or op.get("summary", ""),
                        "parameters": {"type": "object", "properties": props, "required": req}}})
    return out

def call(spec, name, args):
    """Run a tool call the model made. Returns the parsed JSON envelope (errors included)."""
    for path, methods in spec["paths"].items():
        for method, op in methods.items():
            if op["operationId"] != name:
                continue
            args = dict(args)
            for p in op.get("parameters", []):
                if p["in"] == "path":
                    path = path.replace("{%s}" % p["name"], urllib.parse.quote(str(args.pop(p["name"])), safe=""))
            url = spec["servers"][0]["url"] + path
            data = None
            if method == "get":
                if args:
                    url += "?" + urllib.parse.urlencode(args)
            else:
                data = json.dumps(args).encode()
            req = urllib.request.Request(url, data=data, method=method.upper(),
                                         headers={"content-type": "application/json", "x-kd-via": KD_VIA, "user-agent": UA})
            try:
                with urllib.request.urlopen(req, timeout=15) as r:
                    return json.load(r)
            except urllib.error.HTTPError as e:
                return json.load(e)
    raise KeyError(name)

if __name__ == "__main__":
    spec = load_spec()
    print(json.dumps(tools(spec), indent=2))
    res = call(spec, "preflightCheck", {"target": "npm:postmark-mcp@1.0.18", "action": "install"})
    d = res.get("data") or {}
    print(d.get("enforce"), "|", d.get("headline"), "|", res.get("request_id"))
