from __future__ import annotations

import asyncio
import contextlib
import json
from pathlib import Path
from typing import TYPE_CHECKING

from ..models import DecisionStatus
from ..state import StateManager

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable


class RPCServer:
    """
    Unix socket RPC server for programmatic control.

    Protocol: JSON-RPC 2.0 over Unix domain socket.
    Each message is newline-delimited JSON.
    """

    def __init__(self, socket_path: Path, state_manager: StateManager) -> None:
        self.socket_path = socket_path
        self.state_manager = state_manager
        self.server: asyncio.Server | None = None
        self._handlers: dict[
            str, Callable[[dict[str, object]], Awaitable[dict[str, object]]]
        ] = {}
        self._setup_handlers()

    def _setup_handlers(self) -> None:
        """Register RPC method handlers."""
        self._handlers = {
            "get_state": self._handle_get_state,
            "approve": self._handle_approve,
            "deny": self._handle_deny,
            "set_yolo": self._handle_set_yolo,
            "set_session_yolo": self._handle_set_session_yolo,
            "reset": self._handle_reset,
            "get_todos": self._handle_get_todos,
            "health": self._handle_health,
            "subscribe": self._handle_subscribe,
        }

    async def _handle_get_state(self, _params: dict[str, object]) -> dict[str, object]:
        return self.state_manager.state.to_dict()

    async def _handle_approve(self, params: dict[str, object]) -> dict[str, object]:
        request_id = params.get("request_id")
        if not request_id or not isinstance(request_id, str):
            raise ValueError("request_id is required")
        success = await self.state_manager.make_decision(
            request_id, DecisionStatus.APPROVED
        )
        return {"success": success}

    async def _handle_deny(self, params: dict[str, object]) -> dict[str, object]:
        request_id = params.get("request_id")
        if not request_id or not isinstance(request_id, str):
            raise ValueError("request_id is required")
        success = await self.state_manager.make_decision(
            request_id, DecisionStatus.DENIED
        )
        return {"success": success}

    async def _handle_set_yolo(self, params: dict[str, object]) -> dict[str, object]:
        enabled = bool(params.get("enabled", False))
        await self.state_manager.set_yolo_mode(enabled=enabled)
        return {"yolo_mode": enabled}

    async def _handle_set_session_yolo(
        self, params: dict[str, object]
    ) -> dict[str, object]:
        session_id = params.get("session_id")
        if not session_id or not isinstance(session_id, str):
            raise ValueError("session_id is required")
        enabled = bool(params.get("enabled", False))
        success = await self.state_manager.set_session_yolo_mode(
            session_id, enabled=enabled
        )
        return {"success": success, "session_id": session_id, "yolo_mode": enabled}

    async def _handle_reset(self, _params: dict[str, object]) -> dict[str, object]:
        await self.state_manager.reset_session()
        return {"success": True}

    async def _handle_get_todos(self, _params: dict[str, object]) -> dict[str, object]:
        return {"todos": [t.to_dict() for t in self.state_manager.state.todos]}

    async def _handle_health(self, _params: dict[str, object]) -> dict[str, object]:
        return {
            "status": "ok",
            "version": "0.1.0",
            "yolo_mode": self.state_manager.state.yolo_mode,
            "has_pending": self.state_manager.state.pending_request is not None,
        }

    async def _handle_subscribe(self, _params: dict[str, object]) -> dict[str, object]:
        return self.state_manager.state.to_dict()

    async def _handle_client(
        self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
    ) -> None:
        try:
            while True:
                data = await reader.readline()
                if not data:
                    break

                try:
                    request: dict[str, object] = json.loads(data.decode("utf-8"))
                except json.JSONDecodeError as e:
                    response: dict[str, object] = {
                        "jsonrpc": "2.0",
                        "error": {"code": -32700, "message": f"Parse error: {e}"},
                        "id": None,
                    }
                    writer.write((json.dumps(response) + "\n").encode("utf-8"))
                    await writer.drain()
                    continue

                request_id = request.get("id")
                method = request.get("method")
                params_raw = request.get("params", {})
                params: dict[str, object] = (
                    params_raw if isinstance(params_raw, dict) else {}
                )

                if not method or not isinstance(method, str):
                    response = {
                        "jsonrpc": "2.0",
                        "error": {
                            "code": -32600,
                            "message": "Invalid request: method is required",
                        },
                        "id": request_id,
                    }
                else:
                    handler = self._handlers.get(method)
                    if not handler:
                        response = {
                            "jsonrpc": "2.0",
                            "error": {
                                "code": -32601,
                                "message": f"Method not found: {method}",
                            },
                            "id": request_id,
                        }
                    else:
                        try:
                            result = await handler(params)
                            response = {
                                "jsonrpc": "2.0",
                                "result": result,
                                "id": request_id,
                            }
                        except (ValueError, KeyError, TypeError) as e:
                            response = {
                                "jsonrpc": "2.0",
                                "error": {"code": -32000, "message": str(e)},
                                "id": request_id,
                            }

                writer.write((json.dumps(response) + "\n").encode("utf-8"))
                await writer.drain()

        except asyncio.CancelledError:
            pass
        except (OSError, json.JSONDecodeError, KeyError, TypeError, ValueError):
            pass
        finally:
            writer.close()
            with contextlib.suppress(OSError):
                await writer.wait_closed()

    async def start(self) -> None:
        self.socket_path.parent.mkdir(parents=True, exist_ok=True)
        if self.socket_path.exists():
            self.socket_path.unlink()

        self.server = await asyncio.start_unix_server(
            self._handle_client, path=str(self.socket_path)
        )
        self.socket_path.chmod(0o600)

    async def stop(self) -> None:
        if self.server:
            self.server.close()
            await self.server.wait_closed()

        if self.socket_path.exists():
            self.socket_path.unlink()

