@phase2 @domain @lsp @lsp_server_stub
Feature: LSP server stub JSON-RPC protocol
  As a developer integrating LSP into CleverAgents
  I want a minimal LSP server stub that handles core lifecycle methods
  So that I can test the JSON-RPC transport and protocol handshake

  Background:
    Given a mock LSP transport

  # ---------------------------------------------------------------------------
  # Initialize handshake
  # ---------------------------------------------------------------------------

  @lsp_initialize
  Scenario: LSP initialize returns server info and capabilities
    Given an initialize request with id 1
    When the LSP server processes all messages
    Then the response for id 1 should have result
    And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"
    And the result for id 1 should contain serverInfo with version "0.1.0"
    And the result for id 1 should contain capabilities key "textDocumentSync"

  @lsp_initialize
  Scenario: LSP initialize with client info is accepted
    Given an initialize request with id 10 and clientInfo name "test-client"
    When the LSP server processes all messages
    Then the response for id 10 should have result
    And the result for id 10 should contain serverInfo with name "cleveragents-lsp-stub"

  @lsp_initialize
  Scenario: Initialized notification is silently accepted
    Given an initialize request with id 1
    And a notification for method "initialized"
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 1 should have result
    And there should be no error responses

  @lsp_initialize
  Scenario: Initialized notification before initialize is silently ignored
    Given a notification for method "initialized"
    And an exit notification
    When the LSP server processes all messages
    Then there should be no error responses
    And the server exit code should be 1

  # ---------------------------------------------------------------------------
  # Shutdown handshake
  # ---------------------------------------------------------------------------

  @lsp_shutdown
  Scenario: LSP shutdown returns null result
    Given an initialize request with id 1
    And a shutdown request with id 2
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 2 should have null result
    And the server exit code should be 0

  @lsp_shutdown
  Scenario: LSP exit without prior shutdown returns exit code 1
    Given an initialize request with id 1
    And an exit notification
    When the LSP server processes all messages
    Then the server exit code should be 1

  # ---------------------------------------------------------------------------
  # Unsupported methods
  # ---------------------------------------------------------------------------

  @lsp_unsupported
  Scenario: Unsupported method after initialize returns MethodNotFound error
    Given an initialize request with id 1
    And a request for method "textDocument/completion" with id 5
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 5 should have error code -32601
    And the error message for id 5 should contain "Not implemented: textDocument/completion"

  @lsp_unsupported
  Scenario: Unsupported method textDocument/hover after initialize returns error
    Given an initialize request with id 1
    And a request for method "textDocument/hover" with id 6
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 6 should have error code -32601
    And the error message for id 6 should contain "Not implemented: textDocument/hover"

  @lsp_unsupported
  Scenario: Unsupported method before initialize returns ServerNotInitialized
    Given a request for method "textDocument/completion" with id 5
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 5 should have error code -32002
    And the error message for id 5 should contain "Server not initialized"

  @lsp_unsupported
  Scenario: Unsupported notification is silently ignored
    Given a notification for method "textDocument/didOpen"
    And an exit notification
    When the LSP server processes all messages
    Then there should be no error responses

  # ---------------------------------------------------------------------------
  # Invalid JSON-RPC messages
  # ---------------------------------------------------------------------------

  @lsp_invalid
  Scenario: Invalid JSON returns parse error response
    Given raw bytes that are not valid JSON-RPC
    And an exit notification
    When the LSP server processes all messages
    Then a parse error response should have been sent
    And the server should have stopped

  @lsp_invalid
  Scenario: Missing jsonrpc version field returns error
    Given a message with missing jsonrpc version and id 7
    When the LSP server processes all messages
    Then the response for id 7 should have error code -32600
    And the error message for id 7 should contain "jsonrpc version"

  @lsp_invalid
  Scenario: Missing method field returns error
    Given a message with missing method and id 8
    When the LSP server processes all messages
    Then the response for id 8 should have error code -32600
    And the error message for id 8 should contain "method"

  # ---------------------------------------------------------------------------
  # Server lifecycle
  # ---------------------------------------------------------------------------

  @lsp_lifecycle
  Scenario: Server startup reports PID
    Given an initialize request with id 1
    And an exit notification
    When the LSP server processes all messages
    Then the server should have logged its PID

  @lsp_lifecycle
  Scenario: Server handles EOF gracefully
    When the LSP server processes all messages with empty input
    Then the server should have stopped
    And the server exit code should be 1

  # ---------------------------------------------------------------------------
  # A2A facade wiring
  # ---------------------------------------------------------------------------

  @lsp_a2a
  Scenario: LSP server stores provided A2A facade
    Given an LSP server with A2A facade
    And an initialize request with id 1
    When the LSP server processes all messages
    Then the response for id 1 should have result
    And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"
    And the server should hold the provided A2A facade

  @lsp_a2a
  Scenario: LSP server lazily creates A2A facade on property access
    Given an initialize request with id 1
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 1 should have result
    And the server facade should be lazily created

  # ---------------------------------------------------------------------------
  # Full handshake sequence
  # ---------------------------------------------------------------------------

  @lsp_full_handshake
  Scenario: Complete LSP lifecycle (initialize, shutdown, exit)
    Given an initialize request with id 1
    And a shutdown request with id 2
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 1 should have result
    And the response for id 2 should have null result
    And the server exit code should be 0

  # ---------------------------------------------------------------------------
  # Server construction validation
  # ---------------------------------------------------------------------------

  @lsp_construction
  Scenario: LspServer rejects invalid input_stream
    When I try to create an LspServer with invalid input stream
    Then a TypeError should be raised for input stream

  @lsp_construction
  Scenario: LspServer rejects invalid output_stream
    When I try to create an LspServer with invalid output stream
    Then a TypeError should be raised for output stream

  # ---------------------------------------------------------------------------
  # Transport edge cases
  # ---------------------------------------------------------------------------

  @lsp_transport_edge
  Scenario: Server recovers from invalid message and handles subsequent request
    Given a message with missing jsonrpc version and id 7
    And an initialize request with id 1
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 7 should have error code -32600
    And the response for id 1 should have result
    And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"

  @lsp_transport_edge
  Scenario: Request with non-dict params uses empty params
    Given an initialize request with id 1
    And a request with non-dict params and id 20
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 20 should have error code -32601

  @lsp_transport_edge
  Scenario: Negative content length is handled gracefully
    Given a message with negative content length
    When the LSP server processes all messages
    Then the server should have stopped
    And the server exit code should be 1
    And a transport warning should have been logged for "lsp.transport.negative_content_length"

  @lsp_transport_edge
  Scenario: Incomplete message body is handled gracefully
    Given a message with incomplete body
    When the LSP server processes all messages
    Then the server should have stopped
    And the server exit code should be 1
    And a transport warning should have been logged for "lsp.transport.incomplete_read"

  @lsp_transport_edge
  Scenario: Unknown method returns MethodNotFound without data field
    Given an initialize request with id 1
    And a request for method "custom/withData" with id 30
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 30 should have error code -32601
    And the error response for id 30 should not contain a data field

  @lsp_transport_edge
  Scenario: Missing content length header is handled
    Given a message with missing content length header
    When the LSP server processes all messages
    Then the server should have stopped
    And the server exit code should be 1
    And a transport warning should have been logged for "lsp.transport.missing_content_length"

  @lsp_transport_edge
  Scenario: Oversized content length is rejected
    Given a message with content length exceeding the maximum
    And an exit notification
    When the LSP server processes all messages
    Then the server should have stopped
    And the server exit code should be 1
    And a transport warning should have been logged for "lsp.transport.content_length_exceeded"

  @lsp_transport_edge
  Scenario: Too many header lines are rejected
    Given a message with too many header lines
    When the LSP server processes all messages
    Then the server should have stopped
    And the server exit code should be 1
    And a transport warning should have been logged for "lsp.transport.too_many_header_lines"

  @lsp_transport_edge
  Scenario: JSON array body returns InvalidRequest error
    Given a message with a JSON array body
    And an exit notification
    When the LSP server processes all messages
    Then an InvalidRequest error response should have been sent

  @lsp_transport_edge
  Scenario: Non-integer content length is handled gracefully
    Given a message with non-integer content length
    When the LSP server processes all messages
    Then the server should have stopped
    And the server exit code should be 1
    And a transport warning should have been logged for "lsp.transport.invalid_content_length"

  @lsp_transport_edge
  Scenario: Zero content length returns parse error
    Given a message with content length zero
    And an exit notification
    When the LSP server processes all messages
    Then a parse error response should have been sent
    And the server should have stopped

  @lsp_transport_edge
  Scenario: Duplicate content-length headers uses last value
    Given a message with duplicate content-length headers and id 60
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 60 should have result
    And the result for id 60 should contain serverInfo with name "cleveragents-lsp-stub"

  @lsp_transport_edge
  Scenario: Shutdown without prior initialize returns ServerNotInitialized
    Given a shutdown request with id 1
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 1 should have error code -32002
    And the error message for id 1 should contain "Server not initialized"

  @lsp_transport_edge
  Scenario: Initialize without params key uses empty params
    Given a message with initialize request without params key and id 50
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 50 should have result
    And the result for id 50 should contain serverInfo with name "cleveragents-lsp-stub"

  # ---------------------------------------------------------------------------
  # Post-shutdown request rejection (LSP spec §3.16)
  # ---------------------------------------------------------------------------

  @lsp_shutdown
  Scenario: Request after shutdown returns InvalidRequest error
    Given an initialize request with id 1
    And a shutdown request with id 2
    And a request for method "textDocument/completion" with id 3
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 3 should have error code -32600
    And the error message for id 3 should contain "shutting down"

  @lsp_shutdown
  Scenario: Notification after shutdown is silently ignored
    Given an initialize request with id 1
    And a shutdown request with id 2
    And a notification for method "textDocument/didOpen"
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 1 should have result
    And the response for id 2 should have null result
    And there should be no error responses
    And the server exit code should be 0

  # ---------------------------------------------------------------------------
  # Double initialize rejection
  # ---------------------------------------------------------------------------

  @lsp_initialize
  Scenario: Second initialize request returns InvalidRequest error
    Given an initialize request with id 1
    And an initialize request with id 2
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 1 should have result
    And the response for id 2 should have error code -32600
    And the error message for id 2 should contain "already initialized"

  # ---------------------------------------------------------------------------
  # Stream desync recovery (transport error followed by valid message)
  # ---------------------------------------------------------------------------

  @lsp_transport_edge
  Scenario: Server recovers after oversized message with body and processes next request
    Given an oversized message with real body followed by an initialize request with id 1
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 1 should have result
    And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"

  @lsp_transport_edge
  Scenario: Server recovers after invalid content-length header and processes next request
    Given a message with non-integer content length followed by an initialize request with id 1
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 1 should have result
    And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"

  @lsp_transport_edge
  Scenario: Server recovers after too many header lines and processes next request
    Given a message with too many header lines followed by an initialize request with id 1
    And an exit notification
    When the LSP server processes all messages
    Then the response for id 1 should have result
    And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"

  @lsp_transport_edge
  Scenario: EOF during body discard of oversized message is handled gracefully
    Given an oversized message with a short body that triggers EOF during discard
    When the LSP server processes all messages
    Then the server should have stopped
    And the server exit code should be 1
    And a transport warning should have been logged for "lsp.transport.content_length_exceeded"

  @lsp_transport_edge
  Scenario: Oversized single header line is handled gracefully
    Given a message with a header line exceeding the maximum line length
    And an exit notification
    When the LSP server processes all messages
    Then the server should have stopped
    And the server exit code should be 1
    And a transport warning should have been logged for "lsp.transport.too_many_header_lines"

  @lsp_transport_edge
  Scenario: Deeply nested JSON triggers parse error response
    Given a message with deeply nested JSON
    And an exit notification
    When the LSP server processes all messages
    Then a parse error response should have been sent
    And a transport warning should have been logged for "lsp.transport.json_recursion_error"
    And the server should have stopped

  # ---------------------------------------------------------------------------
  # CLI serve command
  # ---------------------------------------------------------------------------

  @lsp_cli_serve
  Scenario: CLI serve command rejects invalid log level
    When I run lsp CLI serve with log level "banana"
    Then the lsp serve CLI should have failed
    And the lsp serve CLI output should contain "Invalid log level"

  @lsp_cli_serve
  Scenario: CLI serve command accepts valid log level and runs
    When I run lsp CLI serve with piped empty stdin
    Then the lsp serve CLI should have exited with code 1
    And the lsp serve CLI output should contain "CleverAgents LSP Server"

  @lsp_cli_serve
  Scenario: CLI serve command with debug log level
    When I run lsp CLI serve with log level "debug" and piped empty stdin
    Then the lsp serve CLI should have exited with code 1
    And the lsp serve CLI output should contain "CleverAgents LSP Server"

  # ---------------------------------------------------------------------------
  # Broken output stream (pipe failure)
  # ---------------------------------------------------------------------------

  @lsp_transport_edge
  Scenario: Broken output stream stops the server gracefully
    Given an initialize request with id 1
    And an exit notification
    When the LSP server processes all messages with a broken output stream
    Then the server should have stopped
    And the server exit code should be 1
    And a transport warning should have been logged for "lsp.transport.write_error"

  # ---------------------------------------------------------------------------
  # String-valued JSON-RPC request id
  # ---------------------------------------------------------------------------

  @lsp_initialize
  Scenario: String request id is echoed correctly
    Given an initialize request with string id "alpha-1"
    And an exit notification
    When the LSP server processes all messages
    Then the response for string id "alpha-1" should have result
    And the result for string id "alpha-1" should contain serverInfo with name "cleveragents-lsp-stub"

  # ---------------------------------------------------------------------------
  # EOF after shutdown without exit notification
  # ---------------------------------------------------------------------------

  @lsp_shutdown
  Scenario: EOF after shutdown without exit returns exit code 0
    Given an initialize request with id 1
    And a shutdown request with id 2
    When the LSP server processes all messages
    Then the response for id 2 should have null result
    And the server exit code should be 0

  # ---------------------------------------------------------------------------
  # Per-message read timeout (DoS protection)
  # ---------------------------------------------------------------------------
  @lsp_transport_edge @lsp_dos_protection
  Scenario: Read timeout is configurable via constructor parameter
    Given an initialize request with id 1
    And an exit notification
    When the LSP server processes all messages with read timeout 60.0
    Then the response for id 1 should have result
    And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"
    And the server exit code should be 1

  @lsp_transport_edge @lsp_dos_protection
  Scenario: MESSAGE_READ_TIMEOUT constant is exported and has correct default value

    When I check the MESSAGE_READ_TIMEOUT constant
    Then the MESSAGE_READ_TIMEOUT should be 30
    And MESSAGE_READ_TIMEOUT should be importable from lsp server module

  @lsp_transport_edge @lsp_dos_protection
  Scenario: Read body times out immediately with read_timeout zero on a real pipe
    When I invoke read body with timeout 0.0 on an empty pipe expecting 10 bytes
    Then the read body result should be None
    And a transport read_timeout warning should be captured

  @lsp_transport_edge @lsp_dos_protection
  Scenario: Read body times out when select reports no data on a real pipe
    When I invoke read body with timeout 0.05 on an empty pipe expecting 10 bytes
    Then the read body result should be None
    And a transport read_timeout warning should be captured
