"""
Versioned IPC protocol helpers for out-of-process plugin runtimes.
"""
from __future__ import annotations

import json
import uuid
from dataclasses import dataclass
from typing import Any

PROTOCOL_VERSION = 1
MIN_SUPPORTED_VERSION = 1
MAX_SUPPORTED_VERSION = 1

# Keep bounded to avoid untrusted plugin/runtime memory blowups.
MAX_FRAME_BYTES = 2 * 1024 * 1024
MAX_BODY_BYTES = 1 * 1024 * 1024
MAX_HEADERS = 64

TRANSPORT = "stdio-jsonl-v1"

EVENT_HEARTBEAT = "heartbeat"
EVENT_SETTINGS_SET = "settings_set"
EVENT_TOOL_STREAM_PROGRESS = "tool_stream_progress"
EVENT_TOOL_STREAM_CHUNK = "tool_stream_chunk"
EVENT_TOOL_STREAM_FINAL = "tool_stream_final"
EVENT_TOOL_STREAM_TIMEOUT = "tool_stream_timeout"
EVENT_CANCEL_ACK = "cancel_ack"

ACTION_HANDSHAKE = "handshake"
ACTION_HEALTH = "health"
ACTION_INVOKE_TOOL = "invoke_tool"
ACTION_INVOKE_HOOK = "invoke_hook"
ACTION_ROUTE_REQUEST = "route_request"
ACTION_SYNC_SETTINGS = "sync_settings"
ACTION_CANCEL_REQUEST = "cancel_request"
ACTION_CONTROL_EVENT = "control_event"


class IpcErrorCode:
    POLICY_DENIED = "policy_denied"
    TIMEOUT = "timeout"
    RUNTIME_CRASH = "runtime_crash"
    PROTOCOL_MISMATCH = "protocol_mismatch"
    AUTH_FAILED = "auth_failed"
    BAD_REQUEST = "bad_request"
    UNKNOWN_ACTION = "unknown_action"
    PAYLOAD_TOO_LARGE = "payload_too_large"
    INTERNAL = "internal"
    QUARANTINED = "quarantined"


@dataclass(frozen=True)
class IpcError:
    code: str
    message: str
    retryable: bool = False
    details: dict[str, Any] | None = None

    def as_dict(self) -> dict[str, Any]:
        out = {
            "code": self.code,
            "message": self.message,
            "retryable": bool(self.retryable),
        }
        if isinstance(self.details, dict) and self.details:
            out["details"] = dict(self.details)
        return out


def make_request(
    *,
    action: str,
    plugin_id: str,
    auth: str,
    payload: dict[str, Any] | None = None,
    request_id: str | None = None,
    capability_context: dict[str, Any] | None = None,
    seq: int = 0,
) -> dict[str, Any]:
    return {
        "v": PROTOCOL_VERSION,
        "kind": "request",
        "request_id": request_id or uuid.uuid4().hex,
        "plugin_id": plugin_id,
        "auth": auth,
        "action": str(action or "").strip(),
        "payload": payload if isinstance(payload, dict) else {},
        "capability_context": capability_context if isinstance(capability_context, dict) else {},
        "seq": int(seq),
        "nonce": uuid.uuid4().hex,
    }


def make_response(
    *,
    request: dict[str, Any],
    ok: bool,
    payload: dict[str, Any] | None = None,
    error: IpcError | None = None,
    seq: int = 0,
) -> dict[str, Any]:
    out = {
        "v": PROTOCOL_VERSION,
        "kind": "response",
        "request_id": str(request.get("request_id") or ""),
        "plugin_id": str(request.get("plugin_id") or ""),
        "ok": bool(ok),
        "payload": payload if isinstance(payload, dict) else {},
        "seq": int(seq),
    }
    if not ok:
        out["error"] = (error or IpcError(IpcErrorCode.INTERNAL, "unknown error")).as_dict()
    return out


def make_event(
    *,
    plugin_id: str,
    auth: str,
    event: str,
    payload: dict[str, Any] | None = None,
    seq: int = 0,
) -> dict[str, Any]:
    return {
        "v": PROTOCOL_VERSION,
        "kind": "event",
        "plugin_id": plugin_id,
        "auth": auth,
        "event": str(event or "").strip(),
        "payload": payload if isinstance(payload, dict) else {},
        "seq": int(seq),
    }


def encode_frame(frame: dict[str, Any]) -> bytes:
    raw = json.dumps(frame, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
    if len(raw) > MAX_FRAME_BYTES:
        raise ValueError("IPC frame exceeds MAX_FRAME_BYTES")
    return raw + b"\n"


def decode_frame(line: bytes | str) -> dict[str, Any]:
    raw = line.encode("utf-8") if isinstance(line, str) else line
    if len(raw) > MAX_FRAME_BYTES:
        raise ValueError("IPC frame exceeds MAX_FRAME_BYTES")
    data = json.loads(raw.decode("utf-8"))
    if not isinstance(data, dict):
        raise ValueError("IPC frame must decode to an object")
    return data


def protocol_is_compatible(version: int) -> bool:
    try:
        v = int(version)
    except Exception:
        return False
    return MIN_SUPPORTED_VERSION <= v <= MAX_SUPPORTED_VERSION

