"""ASV benchmarks for LSP server stub startup latency.

Measures the performance of:
- LspServer construction overhead
- Initialize request handling latency
- Full handshake (initialize + shutdown + exit) latency
- MethodNotFound error response latency
"""

from __future__ import annotations

import importlib
import io
import json
import sys
from pathlib import Path

# Ensure the local *source* tree is importable even when ASV has an
# older build of the package installed.
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
    sys.path.insert(0, _SRC)

# Force-reload so ASV picks up the source tree version.  Reload the
# specific submodule that contains the benchmarked code, not just the
# top-level package.
import cleveragents  # noqa: E402
import cleveragents.lsp.server  # noqa: E402

importlib.reload(cleveragents)
importlib.reload(cleveragents.lsp.server)

from cleveragents.lsp.server import LspServer  # noqa: E402


def _encode_message(msg: dict) -> bytes:
    """Encode a JSON-RPC message with Content-Length framing."""
    payload = json.dumps(msg).encode("utf-8")
    header = f"Content-Length: {len(payload)}\r\n\r\n".encode()
    return header + payload


def _make_streams(*messages: dict) -> tuple[io.BytesIO, io.BytesIO]:
    """Build input/output streams from a sequence of JSON-RPC messages."""
    raw = b"".join(_encode_message(msg) for msg in messages)
    return io.BytesIO(raw), io.BytesIO()


class LspStubStartupSuite:
    """Benchmark LSP server stub startup and request handling."""

    timeout = 60

    def setup(self) -> None:
        """Pre-build common messages and reusable streams for benchmarks."""
        self.init_msg = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "initialize",
            "params": {"capabilities": {}},
        }
        self.shutdown_msg = {
            "jsonrpc": "2.0",
            "id": 2,
            "method": "shutdown",
        }
        self.exit_msg = {
            "jsonrpc": "2.0",
            "method": "exit",
        }
        self.completion_msg = {
            "jsonrpc": "2.0",
            "id": 3,
            "method": "textDocument/completion",
            "params": {},
        }

        # Pre-build the 100-message payload so that list construction
        # overhead is excluded from the benchmark measurement.
        batch_messages: list[dict] = [self.init_msg]
        for i in range(100):
            batch_messages.append(
                {
                    "jsonrpc": "2.0",
                    "id": i + 10,
                    "method": "textDocument/completion",
                    "params": {},
                }
            )
        batch_messages.append(self.exit_msg)
        self._batch_raw = b"".join(_encode_message(msg) for msg in batch_messages)

    def time_server_construction(self) -> None:
        """Measure LspServer construction overhead."""
        inp, out = _make_streams(self.exit_msg)
        LspServer(input_stream=inp, output_stream=out)

    def time_initialize_request(self) -> None:
        """Measure initialize request handling latency."""
        inp, out = _make_streams(self.init_msg, self.exit_msg)
        server = LspServer(input_stream=inp, output_stream=out)
        server.run()

    def time_full_handshake(self) -> None:
        """Measure full lifecycle: initialize + shutdown + exit."""
        inp, out = _make_streams(self.init_msg, self.shutdown_msg, self.exit_msg)
        server = LspServer(input_stream=inp, output_stream=out)
        server.run()

    def time_method_not_found(self) -> None:
        """Measure MethodNotFound error response latency (after initialize)."""
        inp, out = _make_streams(self.init_msg, self.completion_msg, self.exit_msg)
        server = LspServer(input_stream=inp, output_stream=out)
        server.run()

    def time_100_method_not_found(self) -> None:
        """Measure 100 MethodNotFound responses (after initialize)."""
        inp = io.BytesIO(self._batch_raw)
        out = io.BytesIO()
        server = LspServer(input_stream=inp, output_stream=out)
        server.run()
