"""Typed root-session controls backed by the Prime Agent host bridge."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Literal

from rlm import host_request

AgentSessionDelivery = Literal["steer", "follow_up"]
AgentSessionCapability = Literal["send", "recent_turns", "stop", "restart"]


def _field(payload: dict[str, Any], snake: str, camel: str) -> Any:
    value = payload.get(snake)
    return payload.get(camel) if value is None else value


def _required_string(payload: dict[str, Any], snake: str, camel: str, label: str) -> str:
    value = _field(payload, snake, camel)
    if not isinstance(value, str) or not value.strip():
        raise RuntimeError(f"{label} returned an invalid {snake}")
    return value


def _optional_string(payload: dict[str, Any], snake: str, camel: str) -> str | None:
    value = _field(payload, snake, camel)
    if value is None:
        return None
    if not isinstance(value, str):
        raise RuntimeError(f"session entry has an invalid {snake}")
    return value


@dataclass(frozen=True)
class TurnSummary:
    user: str
    assistant: str
    tool_calls: int
    started_at: int | None = None
    ended_at: int | None = None
    message_count: int = 0
    truncated: bool = False


@dataclass(frozen=True)
class AgentSessionRecentTurns:
    turns: list[TurnSummary]
    count: int
    max_chars: int
    truncated: bool


@dataclass(frozen=True)
class AgentSessionSendReceipt:
    operation_id: str
    status: Literal["accepted", "queued"]
    delivery: AgentSessionDelivery
    target_active_session_id: str
    target_session_id: str
    target_session_name: str | None = None


@dataclass(frozen=True)
class AgentSessionOperationReceipt:
    operation_id: str
    status: Literal["accepted"]


@dataclass
class AgentSessionHandle:
    active_session_id: str
    session_id: str
    session_name: str | None = None
    cwd: str | None = None
    runtime_kind: str | None = "top-level"
    status: str | None = None
    is_current: bool = False
    is_streaming: bool = False
    is_compacting: bool = False
    attached_clients: int = 0
    message_count: int = 0
    queued_count: int = 0
    is_session_active: bool = False
    capabilities: tuple[AgentSessionCapability, ...] = field(default=())

    @property
    def id(self) -> str:
        return self.active_session_id

    @property
    def name(self) -> str:
        return self.session_name or self.active_session_id

    @property
    def running(self) -> bool:
        return self.is_session_active or self.is_streaming or self.status in {"running", "busy", "model", "tool"}

    async def refresh(self) -> AgentSessionHandle:
        refreshed = await sessions.get(self.active_session_id)
        self._merge(refreshed)
        return self

    async def info(self) -> dict[str, Any]:
        payload = await _request("get", {"target": self.active_session_id})
        refreshed = _session_from_payload(_required_mapping(payload, "session", "agent.sessions.get"))
        self._merge(refreshed)
        return _session_to_dict(refreshed)

    async def observe(self, count: int = 8, max_chars: int = 800) -> list[TurnSummary]:
        return (await self.recent_turns(count=count, max_chars=max_chars)).turns

    async def recent_turns(self, count: int = 3, max_chars: int = 6_000) -> AgentSessionRecentTurns:
        self._require_capability("recent_turns")
        if not isinstance(count, int) or isinstance(count, bool):
            raise TypeError("count must be an int")
        if not isinstance(max_chars, int) or isinstance(max_chars, bool):
            raise TypeError("max_chars must be an int")
        payload = await _request(
            "recent_turns",
            {"target": self.active_session_id, "count": count, "max_chars": max_chars},
        )
        agent_payload = payload.get("agent")
        if isinstance(agent_payload, dict):
            self._merge(_session_from_payload(agent_payload))
        raw_turns = payload.get("turns")
        if not isinstance(raw_turns, list):
            raise RuntimeError("agent.sessions.recent_turns returned an invalid turns list")
        result_count = payload.get("count", count)
        result_max_chars = payload.get("maxChars", payload.get("max_chars", max_chars))
        truncated = payload.get("truncated", False)
        if (
            isinstance(result_count, bool)
            or not isinstance(result_count, int)
            or isinstance(result_max_chars, bool)
            or not isinstance(result_max_chars, int)
            or not isinstance(truncated, bool)
        ):
            raise RuntimeError("agent.sessions.recent_turns returned invalid metadata")
        return AgentSessionRecentTurns(
            turns=[_turn_from_payload(turn) for turn in raw_turns],
            count=result_count,
            max_chars=result_max_chars,
            truncated=truncated,
        )

    async def send(self, message: str, delivery: AgentSessionDelivery = "follow_up") -> AgentSessionSendReceipt:
        self._require_capability("send")
        if not isinstance(message, str) or not message.strip():
            raise TypeError("message must be a non-empty str")
        if delivery not in {"steer", "follow_up"}:
            raise ValueError("delivery must be 'steer' or 'follow_up'")
        payload = await _request(
            "send",
            {"target": self.active_session_id, "message": message, "delivery": delivery},
        )
        return _send_receipt_from_payload(payload)

    async def stop(self) -> AgentSessionOperationReceipt:
        self._require_capability("stop")
        receipt = _operation_from_payload(await _request("stop", {"target": self.active_session_id}))
        self.status = "stopped"
        self.is_session_active = False
        return receipt

    async def restart(self) -> AgentSessionOperationReceipt:
        self._require_capability("restart")
        return _operation_from_payload(await _request("restart", {"target": self.active_session_id}))

    def _require_capability(self, capability: AgentSessionCapability) -> None:
        if capability not in self.capabilities:
            raise RuntimeError(f"agent session {self.name!r} does not support {capability}")

    def _merge(self, other: AgentSessionHandle) -> None:
        self.active_session_id = other.active_session_id
        self.session_id = other.session_id
        self.session_name = other.session_name
        self.cwd = other.cwd
        self.runtime_kind = other.runtime_kind
        self.status = other.status
        self.is_current = other.is_current
        self.is_streaming = other.is_streaming
        self.is_compacting = other.is_compacting
        self.attached_clients = other.attached_clients
        self.message_count = other.message_count
        self.queued_count = other.queued_count
        self.is_session_active = other.is_session_active
        self.capabilities = other.capabilities


class SessionNamespace:
    async def list(self) -> list[AgentSessionHandle]:
        payload = await _request("list")
        entries = payload.get("sessions")
        if not isinstance(entries, list):
            raise RuntimeError("agent.sessions.list returned an invalid sessions list")
        return [_session_from_payload(entry) for entry in entries]

    async def get(
        self,
        target: str | None = None,
        *,
        active_session_id: str | None = None,
        session_id: str | None = None,
        name: str | None = None,
    ) -> AgentSessionHandle:
        selectors = [value for value in (target, active_session_id, session_id, name) if value is not None]
        if len(selectors) != 1 or not isinstance(selectors[0], str) or not selectors[0].strip():
            raise TypeError("sessions.get requires exactly one non-empty target selector")
        payload = await _request("get", {"target": selectors[0].strip()})
        return _session_from_payload(_required_mapping(payload, "session", "agent.sessions.get"))


sessions = SessionNamespace()


async def _request(operation: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
    result = await host_request(f"agent.sessions.{operation}", payload)
    if not isinstance(result, dict):
        raise RuntimeError(f"agent.sessions.{operation} returned an invalid result")
    return result


def _session_from_payload(payload: Any) -> AgentSessionHandle:
    if not isinstance(payload, dict):
        raise RuntimeError("agent.sessions returned an invalid session entry")
    active_session_id = _required_string(payload, "active_session_id", "activeSessionId", "agent.sessions")
    session_id = _required_string(payload, "session_id", "sessionId", "agent.sessions")
    session_name = _optional_string(payload, "session_name", "sessionName")
    cwd = _optional_string(payload, "cwd", "cwd")
    runtime_kind = _optional_string(payload, "runtime_kind", "runtimeKind")
    status = _optional_string(payload, "status", "status")
    capabilities_raw = payload.get("capabilities", [])
    if not isinstance(capabilities_raw, list) or not all(isinstance(value, str) for value in capabilities_raw):
        raise RuntimeError("agent.sessions entry has invalid capabilities")
    capabilities = tuple(value for value in capabilities_raw if value in {"send", "recent_turns", "stop", "restart"})
    return AgentSessionHandle(
        active_session_id=active_session_id,
        session_id=session_id,
        session_name=session_name,
        cwd=cwd,
        runtime_kind=runtime_kind,
        status=status,
        is_current=_bool_field(payload, "is_current", "isCurrent"),
        is_streaming=_bool_field(payload, "is_streaming", "isStreaming"),
        is_compacting=_bool_field(payload, "is_compacting", "isCompacting"),
        attached_clients=_int_field(payload, "attached_clients", "attachedClients"),
        message_count=_int_field(payload, "message_count", "messageCount"),
        queued_count=_int_field(payload, "queued_count", "queuedCount"),
        is_session_active=_bool_field(payload, "is_session_active", "isSessionActive"),
        capabilities=capabilities,
    )


def _session_to_dict(session: AgentSessionHandle) -> dict[str, Any]:
    return {
        "active_session_id": session.active_session_id,
        "session_id": session.session_id,
        "session_name": session.session_name,
        "cwd": session.cwd,
        "runtime_kind": session.runtime_kind,
        "status": session.status,
        "is_current": session.is_current,
        "is_streaming": session.is_streaming,
        "is_compacting": session.is_compacting,
        "attached_clients": session.attached_clients,
        "message_count": session.message_count,
        "queued_count": session.queued_count,
        "is_session_active": session.is_session_active,
        "capabilities": list(session.capabilities),
    }


def _turn_from_payload(payload: Any) -> TurnSummary:
    if not isinstance(payload, dict):
        raise RuntimeError("agent.sessions.recent_turns returned an invalid turn")
    user = payload.get("user")
    assistant = payload.get("assistant")
    tool_calls = payload.get("toolCalls", payload.get("tool_calls"))
    started_at = payload.get("startedAt", payload.get("started_at"))
    ended_at = payload.get("endedAt", payload.get("ended_at"))
    message_count = payload.get("messageCount", payload.get("message_count", 0))
    truncated = payload.get("truncated", False)
    if (
        not isinstance(user, str)
        or not isinstance(assistant, str)
        or isinstance(tool_calls, bool)
        or not isinstance(tool_calls, int)
        or tool_calls < 0
        or (started_at is not None and (isinstance(started_at, bool) or not isinstance(started_at, int)))
        or (ended_at is not None and (isinstance(ended_at, bool) or not isinstance(ended_at, int)))
        or isinstance(message_count, bool)
        or not isinstance(message_count, int)
        or message_count < 0
        or not isinstance(truncated, bool)
    ):
        raise RuntimeError("agent.sessions.recent_turns returned an invalid turn")
    return TurnSummary(user, assistant, tool_calls, started_at, ended_at, message_count, truncated)


def _send_receipt_from_payload(payload: dict[str, Any]) -> AgentSessionSendReceipt:
    operation_id = _required_string(payload, "operation_id", "operationId", "agent.sessions.send")
    status = payload.get("status")
    delivery = payload.get("delivery")
    target = payload.get("target")
    if status not in {"accepted", "queued"} or delivery not in {"steer", "follow_up"} or not isinstance(target, dict):
        raise RuntimeError("agent.sessions.send returned an invalid receipt")
    target_active = _required_string(target, "active_session_id", "activeSessionId", "agent.sessions.send")
    target_session = _required_string(target, "session_id", "sessionId", "agent.sessions.send")
    target_name = _optional_string(target, "session_name", "sessionName")
    return AgentSessionSendReceipt(operation_id, status, delivery, target_active, target_session, target_name)


def _operation_from_payload(payload: dict[str, Any]) -> AgentSessionOperationReceipt:
    operation_id = _required_string(payload, "operation_id", "operationId", "agent.sessions.operation")
    if payload.get("status") != "accepted":
        raise RuntimeError("agent.sessions operation returned an invalid status")
    return AgentSessionOperationReceipt(operation_id, "accepted")


def _required_mapping(payload: dict[str, Any], key: str, operation: str) -> dict[str, Any]:
    value = payload.get(key)
    if not isinstance(value, dict):
        raise RuntimeError(f"{operation} returned an invalid {key}")
    return value


def _bool_field(payload: dict[str, Any], snake: str, camel: str) -> bool:
    value = _field(payload, snake, camel)
    return value if isinstance(value, bool) else False


def _int_field(payload: dict[str, Any], snake: str, camel: str) -> int:
    value = _field(payload, snake, camel)
    return value if isinstance(value, int) and not isinstance(value, bool) else 0
