#!/usr/bin/env python3
"""SOAGILA Native Messaging Host for browser extensions.

Chrome/Edge/Firefox extensions cannot read arbitrary files from the user's
home directory, so they cannot fetch ``~/.soagila/daemon_token`` directly.
Instead the browser extension speaks to this native host via Chrome's
Native Messaging API (stdin/stdout length-prefixed JSON), and this host
reads the token + port from disk and proxies the call to the daemon over
HTTP.

Installation notes:
    - Windows: registry key under
        HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\ai.soagila.host
      with a JSON manifest pointing to this script (or its .bat wrapper).
    - macOS:  ~/Library/Application Support/Google/Chrome/NativeMessagingHosts/
        ai.soagila.host.json
    - Linux:  ~/.config/google-chrome/NativeMessagingHosts/ai.soagila.host.json

Protocol (Chrome native messaging):
    input:  4-byte little-endian length header + UTF-8 JSON body
    output: same format

Supported messages (from extension):
    { "action": "health" }
    { "action": "call_tool", "tool": "soagila_recall", "arguments": {...} }
    { "action": "get_status" }

All messages return ``{ "success": bool, "data"|"error": ... }``.
"""

from __future__ import annotations

import json
import struct
import sys
import traceback
import urllib.error
import urllib.request
from pathlib import Path


def _soagila_home() -> Path:
    return Path.home() / ".soagila"


def _read_port() -> int | None:
    path = _soagila_home() / "daemon_port"
    if not path.exists():
        return None
    try:
        return int(path.read_text(encoding="utf-8").strip())
    except (OSError, ValueError):
        return None


def _read_token() -> str | None:
    path = _soagila_home() / "daemon_token"
    if not path.exists():
        return None
    try:
        return path.read_text(encoding="utf-8").strip() or None
    except OSError:
        return None


def _http_call(method: str, path: str, body: dict | None) -> dict:
    """Blocking HTTP call to the daemon with Bearer auth."""
    port = _read_port()
    token = _read_token()
    if port is None or token is None:
        return {
            "success": False,
            "error": "SOAGILA Daemon is not running",
            "code": "DAEMON_OFFLINE",
        }

    url = f"http://127.0.0.1:{port}{path}"
    data = json.dumps(body).encode() if body is not None else None
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {token}",
        "X-Client-Type": "browser",
    }

    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as exc:
        try:
            return json.loads(exc.read().decode("utf-8"))
        except Exception:
            return {"success": False, "error": f"HTTP {exc.code}"}
    except urllib.error.URLError as exc:
        return {
            "success": False,
            "error": f"Daemon unreachable: {exc.reason}",
            "code": "DAEMON_UNREACHABLE",
        }


# ── Chrome native messaging framing ─────────────────────────────────────


def _send(msg: dict) -> None:
    """Write a message using Chrome's 4-byte length prefix format."""
    body = json.dumps(msg).encode("utf-8")
    sys.stdout.buffer.write(struct.pack("<I", len(body)))
    sys.stdout.buffer.write(body)
    sys.stdout.buffer.flush()


def _recv() -> dict | None:
    """Read one message from Chrome (None on EOF)."""
    header = sys.stdin.buffer.read(4)
    if len(header) < 4:
        return None
    length = struct.unpack("<I", header)[0]
    body = sys.stdin.buffer.read(length)
    if len(body) < length:
        return None
    return json.loads(body.decode("utf-8"))


# ── Message dispatch ────────────────────────────────────────────────────


_ROUTE_MAP: dict[str, tuple[str, str]] = {
    "soagila_remember":   ("POST", "/tools/remember"),
    "soagila_recall":     ("POST", "/tools/recall"),
    "soagila_status":     ("GET",  "/tools/status"),
    "soagila_receipt":    ("GET",  "/tools/receipt"),
    "soagila_inspect":    ("POST", "/tools/inspect"),
    "soagila_annihilate": ("POST", "/tools/annihilate"),
    "soagila_prime":      ("POST", "/tools/prime"),
    "soagila_backup":     ("POST", "/tools/backup"),
    "soagila_enterprise": ("POST", "/tools/enterprise"),
    "soagila_crossagent": ("POST", "/tools/crossagent"),
    "soagila_inherit":    ("POST", "/tools/inherit"),
}


def _dispatch(msg: dict) -> dict:
    action = msg.get("action")

    if action == "health":
        return _http_call("GET", "/health", None)

    if action == "call_tool":
        tool = msg.get("tool", "")
        args = msg.get("arguments", {})
        route = _ROUTE_MAP.get(tool)
        if route is None:
            return {
                "success": False,
                "error": f"Unknown tool: {tool}",
                "code": "UNKNOWN_TOOL",
            }
        method, path = route
        if method == "GET" and tool == "soagila_receipt" and args.get("receipt_id"):
            path = f"{path}?id={args['receipt_id']}"
            return _http_call("GET", path, None)
        return _http_call(method, path, args if method != "GET" else None)

    if action == "get_status":
        return _http_call("GET", "/tools/status", None)

    return {
        "success": False,
        "error": f"Unknown action: {action}",
        "code": "UNKNOWN_ACTION",
    }


def main() -> int:
    try:
        while True:
            msg = _recv()
            if msg is None:
                return 0
            try:
                response = _dispatch(msg)
            except Exception as exc:
                response = {
                    "success": False,
                    "error": str(exc),
                    "code": "HOST_EXCEPTION",
                    "trace": traceback.format_exc(),
                }
            _send(response)
    except KeyboardInterrupt:
        return 0
    except Exception as exc:
        # Last-ditch: send error back via stderr then exit
        sys.stderr.write(f"[soagila-native-host] fatal: {exc}\n")
        return 1


if __name__ == "__main__":
    sys.exit(main())
