"""
RPC Client - Lightweight HTTP client for CLI -> Daemon communication.

Used by CLI processes to send skill execution requests to the persistent
daemon service, instead of starting their own WebSocket server.
"""

import asyncio
import json
import logging
import sys
from typing import Any, Dict, Optional

from .constants import DEFAULT_LOG_DIR

logger = logging.getLogger(__name__)

DEFAULT_RPC_HOST = "127.0.0.1"
DEFAULT_RPC_PORT = 8766
DEFAULT_TIMEOUT = 300  # 5 minutes max for a skill execution


class RPCError(Exception):
    """Raised when the RPC call fails."""
    pass


class DaemonNotRunningError(RPCError):
    """Raised when the daemon is not running."""
    pass


async def _http_request(host: str, port: int, method: str, path: str,
                        body: Optional[dict] = None, timeout: float = DEFAULT_TIMEOUT) -> dict:
    """Send an HTTP request using asyncio streams (no external dependencies).

    Returns the parsed JSON response body.
    """
    try:
        reader, writer = await asyncio.wait_for(
            asyncio.open_connection(host, port),
            timeout=10  # connection timeout
        )
    except (ConnectionRefusedError, OSError) as e:
        raise DaemonNotRunningError(
            f"Cannot connect to daemon at {host}:{port}. "
            f"Is the daemon running? Start it with: qqbrowser-skill serve\n"
            f"  Error: {e}"
        )

    try:
        # Build request
        body_bytes = b""
        if body is not None:
            body_bytes = json.dumps(body, ensure_ascii=False).encode("utf-8")

        request_lines = [
            f"{method} {path} HTTP/1.1",
            f"Host: {host}:{port}",
            f"Content-Type: application/json",
            f"Content-Length: {len(body_bytes)}",
            f"Connection: close",
            "",
            "",
        ]
        request = "\r\n".join(request_lines).encode("utf-8") + body_bytes
        writer.write(request)
        await writer.drain()

        # Read response headers line by line
        raw_headers = b""
        while True:
            line = await asyncio.wait_for(reader.readline(), timeout=timeout)
            raw_headers += line
            if line == b"\r\n" or line == b"\n" or line == b"":
                break

        header_str = raw_headers.decode("utf-8", errors="replace")
        header_lines = header_str.strip().split("\r\n")
        if not header_lines:
            raise RPCError("Invalid HTTP response from daemon: empty headers")

        # Parse status line
        status_line = header_lines[0]
        parts = status_line.split(" ", 2)
        status_code = int(parts[1]) if len(parts) >= 2 else 0

        # Parse headers to find Content-Length
        content_length = -1
        for hl in header_lines[1:]:
            if ":" in hl:
                key, val = hl.split(":", 1)
                if key.strip().lower() == "content-length":
                    try:
                        content_length = int(val.strip())
                    except ValueError:
                        pass

        # Read body based on Content-Length or read until connection closes
        MAX_BODY = 100 * 1024 * 1024  # 100 MB max
        if content_length >= 0:
            if content_length > MAX_BODY:
                raise RPCError(f"Response too large: {content_length} bytes")
            body_bytes_resp = await asyncio.wait_for(
                reader.readexactly(content_length), timeout=timeout
            )
        else:
            # No Content-Length: read until EOF (Connection: close)
            chunks = []
            total = 0
            while True:
                chunk = await asyncio.wait_for(reader.read(65536), timeout=timeout)
                if not chunk:
                    break
                chunks.append(chunk)
                total += len(chunk)
                if total > MAX_BODY:
                    raise RPCError(f"Response too large: >{MAX_BODY} bytes")
            body_bytes_resp = b"".join(chunks)

        body_part = body_bytes_resp.decode("utf-8", errors="replace")

        # Parse JSON body
        try:
            result = json.loads(body_part)
        except json.JSONDecodeError:
            raise RPCError(f"Invalid JSON response from daemon: {body_part[:200]}")

        if status_code >= 400:
            error_msg = result.get("error", f"HTTP {status_code}")
            raise RPCError(f"Daemon returned error: {error_msg}")

        return result

    finally:
        writer.close()
        try:
            await writer.wait_closed()
        except Exception:
            pass


async def check_health(host: str = DEFAULT_RPC_HOST,
                       port: int = DEFAULT_RPC_PORT) -> bool:
    """Check if the daemon is healthy."""
    try:
        result = await _http_request(host, port, "GET", "/health", timeout=5)
        return result.get("status") == "ok"
    except Exception:
        return False


async def get_status(host: str = DEFAULT_RPC_HOST,
                     port: int = DEFAULT_RPC_PORT) -> dict:
    """Get daemon status."""
    return await _http_request(host, port, "GET", "/status", timeout=5)


async def execute_skill(skill_name: str, args: Dict[str, Any],
                        log_dir: str = DEFAULT_LOG_DIR,
                        host: str = DEFAULT_RPC_HOST,
                        port: int = DEFAULT_RPC_PORT,
                        timeout: float = DEFAULT_TIMEOUT) -> dict:
    """Execute a skill via the daemon's RPC endpoint.

    Args:
        skill_name: Name of the skill to execute.
        args: Skill arguments dict.
        log_dir: Log directory to pass to the daemon.
        host: Daemon RPC host.
        port: Daemon RPC port.
        timeout: Max wait time for the skill execution.

    Returns:
        Dict with 'success' and 'result' (or 'error') keys.
    """
    body = {
        "skill_name": skill_name,
        "args": args,
        "log_dir": log_dir,
    }
    return await _http_request(host, port, "POST", "/execute", body=body, timeout=timeout)


def get_rpc_port_from_state() -> int:
    """Read the RPC port from the daemon state file, falling back to default."""
    try:
        from .daemon_server import read_state_file, DEFAULT_RPC_PORT
        state = read_state_file()
        if state and "rpc_port" in state:
            return state["rpc_port"]
    except Exception:
        pass
    return DEFAULT_RPC_PORT
