"""
Phase 81: Remote Execution — Cross-node tool execution.

Agent on Node A calls a tool registered on Node B. Simulates HTTP POST
for local development; production would use real network transport.

Storage:
  state/broker/remote_exec/
    nodes/             — one JSON file per registered node
    log.jsonl          — execution audit log
"""

import json
import logging
import time
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

from brynq.server import BROKER_DIR, _write_json_sync, _read_jsonl_sync, _append_jsonl_sync

logger = logging.getLogger(__name__)

# ── Paths ──────────────────────────────────────────────────────────────────

EXEC_DIR = BROKER_DIR / "remote_exec"
NODES_DIR = EXEC_DIR / "nodes"
LOG_FILE = EXEC_DIR / "log.jsonl"

for _d in (EXEC_DIR, NODES_DIR):
    _d.mkdir(parents=True, exist_ok=True)

# ── In-memory handler registry (simulates remote endpoints) ───────────────

_local_handlers: Dict[str, Dict[str, Any]] = {}


def reset() -> None:
    """Reset in-memory state. Used by tests."""
    global _local_handlers
    _local_handlers = {}


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _node_file(node_id: str) -> Path:
    safe = node_id.replace("/", "_").replace("\\", "_")
    return NODES_DIR / f"{safe}.json"


def _load_node(node_id: str) -> Optional[dict]:
    f = _node_file(node_id)
    if not f.exists():
        return None
    try:
        return json.loads(f.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def _save_node(node: dict) -> None:
    _write_json_sync(_node_file(node["node_id"]), node)


# ── Public API ─────────────────────────────────────────────────────────────

def register_node(
    node_id: str,
    capabilities: List[str],
    endpoint_url: Optional[str] = None,
    tools: Optional[Dict[str, Any]] = None,
) -> dict:
    """Register a remote node with its capabilities and optional tool handlers."""
    node = {
        "node_id": node_id,
        "capabilities": capabilities,
        "endpoint_url": endpoint_url or f"http://localhost:0/{node_id}",
        "tools": list((tools or {}).keys()),
        "registered_at": _now(),
        "status": "online",
        "last_ping": _now(),
    }
    _save_node(node)
    if tools:
        _local_handlers[node_id] = tools
    logger.info("Registered remote node: %s with %d capabilities", node_id, len(capabilities))
    return node


def list_nodes(capability: Optional[str] = None) -> List[dict]:
    """List all registered nodes, optionally filtered by capability."""
    nodes = []
    if not NODES_DIR.exists():
        return nodes
    for f in sorted(NODES_DIR.glob("*.json")):
        try:
            node = json.loads(f.read_text("utf-8"))
            if capability and capability not in node.get("capabilities", []):
                continue
            nodes.append(node)
        except (json.JSONDecodeError, OSError):
            continue
    return nodes


def get_node(node_id: str) -> Optional[dict]:
    """Get a single node by ID."""
    return _load_node(node_id)


def remove_node(node_id: str) -> bool:
    """Remove a registered node."""
    f = _node_file(node_id)
    if not f.exists():
        return False
    f.unlink()
    _local_handlers.pop(node_id, None)
    return True


def execute_remote(node_id: str, tool_name: str, arguments: Optional[dict] = None) -> dict:
    """Execute a tool on a remote node. Simulates HTTP POST locally."""
    node = _load_node(node_id)
    if not node:
        return {"ok": False, "error": f"Node '{node_id}' not found"}
    if node.get("status") != "online":
        return {"ok": False, "error": f"Node '{node_id}' is {node.get('status', 'unknown')}"}

    start = time.time()
    exec_id = uuid.uuid4().hex[:12]

    # Try local handler simulation
    handlers = _local_handlers.get(node_id, {})
    if tool_name in handlers:
        try:
            result = handlers[tool_name](arguments or {})
            elapsed = time.time() - start
            log_entry = {
                "exec_id": exec_id,
                "node_id": node_id,
                "tool": tool_name,
                "ts": _now(),
                "elapsed_ms": round(elapsed * 1000, 2),
                "ok": True,
            }
            _append_jsonl_sync(LOG_FILE, log_entry)
            return {"ok": True, "result": result, "exec_id": exec_id, "elapsed_ms": log_entry["elapsed_ms"]}
        except Exception as exc:
            elapsed = time.time() - start
            log_entry = {
                "exec_id": exec_id,
                "node_id": node_id,
                "tool": tool_name,
                "ts": _now(),
                "elapsed_ms": round(elapsed * 1000, 2),
                "ok": False,
                "error": str(exc),
            }
            _append_jsonl_sync(LOG_FILE, log_entry)
            return {"ok": False, "error": str(exc), "exec_id": exec_id}

    # Tool not available on node
    return {"ok": False, "error": f"Tool '{tool_name}' not available on node '{node_id}'"}


def find_node_for_tool(tool_name: str) -> Optional[dict]:
    """Find the best node that has a specific tool."""
    for node in list_nodes():
        if tool_name in node.get("tools", []):
            if node.get("status") == "online":
                return node
    return None


def get_execution_log(node_id: Optional[str] = None, limit: int = 20) -> List[dict]:
    """Get recent execution log entries."""
    entries = _read_jsonl_sync(LOG_FILE)
    if node_id:
        entries = [e for e in entries if e.get("node_id") == node_id]
    return entries[-limit:]


def ping_node(node_id: str) -> dict:
    """Health check a remote node. Updates last_ping timestamp."""
    node = _load_node(node_id)
    if not node:
        return {"ok": False, "error": f"Node '{node_id}' not found"}
    node["last_ping"] = _now()
    _save_node(node)
    return {"ok": True, "node_id": node_id, "status": node.get("status", "unknown"), "last_ping": node["last_ping"]}
