"""Integration tests for the clice MasterServer using pygls."""

import asyncio

import pytest
from lsprotocol.types import (
    CodeActionContext,
    CodeActionParams,
    CompletionParams,
    DefinitionParams,
    DidCloseTextDocumentParams,
    DidChangeTextDocumentParams,
    DidSaveTextDocumentParams,
    DocumentLinkParams,
    DocumentSymbolParams,
    FoldingRangeParams,
    HoverParams,
    InlayHintParams,
    Position,
    Range,
    SemanticTokensParams,
    SignatureHelpParams,
    TextDocumentContentChangeWholeDocument,
    TextDocumentIdentifier,
    VersionedTextDocumentIdentifier,
)


def _doc(uri: str) -> TextDocumentIdentifier:
    return TextDocumentIdentifier(uri=uri)


@pytest.mark.workspace("hello_world")
async def test_server_info(client, workspace):
    assert client.init_result.server_info.name == "clice"
    assert client.init_result.server_info.version == "0.1.0"


@pytest.mark.workspace("hello_world")
async def test_capabilities(client, workspace):
    caps = client.init_result.capabilities
    assert caps.hover_provider is True
    assert caps.completion_provider is not None
    assert caps.definition_provider is True
    assert caps.document_symbol_provider is True
    assert caps.folding_range_provider is True
    assert caps.inlay_hint_provider is True
    assert caps.code_action_provider is True
    assert caps.semantic_tokens_provider is not None


@pytest.mark.workspace("hello_world")
async def test_did_open_close_cycle(client, workspace):
    uri, _ = client.open(workspace / "main.cpp")
    await asyncio.sleep(0.5)
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_shutdown_exit(client, workspace):
    await client.shutdown_async(None)


@pytest.mark.workspace("hello_world")
async def test_feature_requests_after_close(client, workspace):
    uri, _ = client.open(workspace / "main.cpp")
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
    result = await client.text_document_hover_async(
        HoverParams(text_document=_doc(uri), position=Position(line=0, character=0))
    )
    assert result is None


@pytest.mark.workspace("hello_world")
async def test_incremental_change(client, workspace):
    uri, content = client.open(workspace / "main.cpp")
    for i in range(5):
        content += f"\n// change {i}"
        client.text_document_did_change(
            DidChangeTextDocumentParams(
                text_document=VersionedTextDocumentIdentifier(uri=uri, version=i + 1),
                content_changes=[TextDocumentContentChangeWholeDocument(text=content)],
            )
        )
        await asyncio.sleep(0.05)
    await asyncio.sleep(1)
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_diagnostics_received(client, workspace):
    uri, _ = await client.open_and_wait(workspace / "main.cpp")
    assert uri in client.diagnostics
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_hover_before_compile(client, workspace):
    uri, _ = client.open(workspace / "main.cpp")
    result = await client.text_document_hover_async(
        HoverParams(text_document=_doc(uri), position=Position(line=0, character=0))
    )
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_completion_request(client, workspace):
    uri, _ = await client.open_and_wait(workspace / "main.cpp")
    result = await client.text_document_completion_async(
        CompletionParams(
            text_document=_doc(uri), position=Position(line=0, character=0)
        )
    )
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_signature_help_request(client, workspace):
    uri, _ = await client.open_and_wait(workspace / "main.cpp")
    result = await client.text_document_signature_help_async(
        SignatureHelpParams(
            text_document=_doc(uri), position=Position(line=0, character=0)
        )
    )
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_definition_request(client, workspace):
    uri, _ = await client.open_and_wait(workspace / "main.cpp")
    result = await client.text_document_definition_async(
        DefinitionParams(
            text_document=_doc(uri), position=Position(line=0, character=4)
        )
    )
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_document_symbol_request(client, workspace):
    uri, _ = await client.open_and_wait(workspace / "main.cpp")
    result = await client.text_document_document_symbol_async(
        DocumentSymbolParams(text_document=_doc(uri))
    )
    assert result is not None
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_folding_range_request(client, workspace):
    uri, _ = await client.open_and_wait(workspace / "main.cpp")
    result = await client.text_document_folding_range_async(
        FoldingRangeParams(text_document=_doc(uri))
    )
    assert result is not None
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_semantic_tokens_request(client, workspace):
    uri, _ = await client.open_and_wait(workspace / "main.cpp")
    result = await client.text_document_semantic_tokens_full_async(
        SemanticTokensParams(text_document=_doc(uri))
    )
    assert result is not None
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_inlay_hint_request(client, workspace):
    uri, _ = await client.open_and_wait(workspace / "main.cpp")
    result = await client.text_document_inlay_hint_async(
        InlayHintParams(
            text_document=_doc(uri),
            range=Range(
                start=Position(line=0, character=0), end=Position(line=10, character=0)
            ),
        )
    )
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_code_action_request(client, workspace):
    uri, _ = await client.open_and_wait(workspace / "main.cpp")
    result = await client.text_document_code_action_async(
        CodeActionParams(
            text_document=_doc(uri),
            range=Range(
                start=Position(line=0, character=0), end=Position(line=0, character=10)
            ),
            context=CodeActionContext(diagnostics=[]),
        )
    )
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_document_link_request(client, workspace):
    uri, _ = await client.open_and_wait(workspace / "main.cpp")
    result = await client.text_document_document_link_async(
        DocumentLinkParams(text_document=_doc(uri))
    )
    assert result is not None
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_rapid_changes_stress(client, workspace):
    uri, content = client.open(workspace / "main.cpp")
    for i in range(20):
        content += f"\n// stress change {i}\n"
        client.text_document_did_change(
            DidChangeTextDocumentParams(
                text_document=VersionedTextDocumentIdentifier(uri=uri, version=i + 1),
                content_changes=[TextDocumentContentChangeWholeDocument(text=content)],
            )
        )
    await asyncio.sleep(2)
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_save_notification(client, workspace):
    uri, _ = client.open(workspace / "main.cpp")
    await asyncio.sleep(0.5)
    client.text_document_did_save(DidSaveTextDocumentParams(text_document=_doc(uri)))
    await asyncio.sleep(0.5)
    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))


@pytest.mark.workspace("hello_world")
async def test_hover_on_unknown_file(client, workspace):
    result = await client.text_document_hover_async(
        HoverParams(
            text_document=_doc("file:///nonexistent/fake.cpp"),
            position=Position(line=0, character=0),
        )
    )
    assert result is None


@pytest.mark.workspace("hello_world")
async def test_all_features_after_compile_wait(client, workspace):
    """Exercise all feature requests after compilation completes."""
    uri, _ = await client.open_and_wait(workspace / "main.cpp")

    hover = await client.text_document_hover_async(
        HoverParams(text_document=_doc(uri), position=Position(line=0, character=4))
    )
    assert hover is not None

    completion = await client.text_document_completion_async(
        CompletionParams(
            text_document=_doc(uri), position=Position(line=5, character=18)
        )
    )

    await client.text_document_signature_help_async(
        SignatureHelpParams(
            text_document=_doc(uri), position=Position(line=0, character=0)
        )
    )

    await client.text_document_definition_async(
        DefinitionParams(
            text_document=_doc(uri), position=Position(line=0, character=4)
        )
    )

    symbols = await client.text_document_document_symbol_async(
        DocumentSymbolParams(text_document=_doc(uri))
    )
    assert symbols is not None

    folding = await client.text_document_folding_range_async(
        FoldingRangeParams(text_document=_doc(uri))
    )
    assert folding is not None

    tokens = await client.text_document_semantic_tokens_full_async(
        SemanticTokensParams(text_document=_doc(uri))
    )
    assert tokens is not None

    links = await client.text_document_document_link_async(
        DocumentLinkParams(text_document=_doc(uri))
    )
    assert links is not None

    await client.text_document_code_action_async(
        CodeActionParams(
            text_document=_doc(uri),
            range=Range(
                start=Position(line=0, character=0), end=Position(line=0, character=10)
            ),
            context=CodeActionContext(diagnostics=[]),
        )
    )

    await client.text_document_inlay_hint_async(
        InlayHintParams(
            text_document=_doc(uri),
            range=Range(
                start=Position(line=0, character=0), end=Position(line=10, character=0)
            ),
        )
    )

    client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
