"""
Rigorous tests for Haskell Language Server integration with Serena.

Tests prove that Serena's symbol tools can:
1. Discover all expected symbols with precise matching
2. Track cross-file references accurately
3. Identify data type structures and record fields
4. Navigate between definitions and usages

Test Repository Structure:
- src/Calculator.hs: Calculator data type, arithmetic functions (add, subtract, multiply, divide, calculate)
- src/Helper.hs: Helper functions (validateNumber, isPositive, isNegative, absolute)
- app/Main.hs: Main entry point using Calculator and Helper modules
"""

import sys

import pytest

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics


@pytest.mark.haskell
@pytest.mark.skipif(sys.platform == "win32", reason="HLS not installed on Windows CI")
class TestHaskellLanguageServer:
    @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
    def test_calculator_module_symbols(self, language_server: SolidLanguageServer):
        """
        Test precise symbol discovery in Calculator.hs.

        Verifies that Serena can identify:
        - Data type definition (Calculator with record fields)
        - All exported functions with correct names
        - Module structure
        """
        all_symbols, _ = language_server.request_document_symbols("src/Calculator.hs").get_all_symbols_and_roots()
        symbol_names = {s["name"] for s in all_symbols}

        # Verify exact set of expected top-level symbols
        expected_symbols = {
            "Calculator",  # Data type
            "add",  # Function: Int -> Int -> Int
            "subtract",  # Function: Int -> Int -> Int
            "multiply",  # Function: Int -> Int -> Int
            "divide",  # Function: Int -> Int -> Maybe Int
            "calculate",  # Function: Calculator -> String -> Int -> Int -> Maybe Int
        }

        # Verify all expected symbols are present
        missing = expected_symbols - symbol_names
        assert not missing, f"Missing expected symbols in Calculator.hs: {missing}"

        # Verify Calculator data type exists
        calculator_symbol = next((s for s in all_symbols if s["name"] == "Calculator"), None)
        assert calculator_symbol is not None, "Calculator data type not found"

        # The Calculator should be identified as a data type
        # HLS may use different SymbolKind values (1=File, 5=Class, 23=Struct)
        assert calculator_symbol["kind"] in [
            1,
            5,
            23,
        ], f"Calculator should be a data type (kind 1, 5, or 23), got kind {calculator_symbol['kind']}"

    @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
    def test_helper_module_symbols(self, language_server: SolidLanguageServer):
        """
        Test precise symbol discovery in Helper.hs.

        Verifies Serena identifies all helper functions that are imported
        and used by Calculator module.
        """
        all_symbols, _ = language_server.request_document_symbols("src/Helper.hs").get_all_symbols_and_roots()
        symbol_names = {s["name"] for s in all_symbols}

        # Verify expected helper functions (module name may also appear)
        expected_symbols = {
            "validateNumber",  # Function used by Calculator.add and Calculator.subtract
            "isPositive",  # Predicate function
            "isNegative",  # Predicate function used by absolute
            "absolute",  # Function that uses isNegative
        }

        # All expected symbols should be present (module name is optional)
        missing = expected_symbols - symbol_names
        assert not missing, f"Missing expected symbols in Helper.hs: {missing}"

        # Verify no unexpected symbols beyond the module name
        extra = symbol_names - expected_symbols - {"Helper"}
        assert not extra, f"Unexpected symbols in Helper.hs: {extra}"

    @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
    def test_main_module_imports(self, language_server: SolidLanguageServer):
        """
        Test that Main.hs properly references both Calculator and Helper modules.

        Verifies Serena can identify cross-module dependencies.
        """
        all_symbols, _ = language_server.request_document_symbols("app/Main.hs").get_all_symbols_and_roots()
        symbol_names = {s["name"] for s in all_symbols}

        # Main.hs should have the main function
        assert "main" in symbol_names, "Main.hs should contain 'main' function"

    @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
    def test_cross_file_references_validateNumber(self, language_server: SolidLanguageServer):
        """
        Test cross-file reference tracking for validateNumber function.

        validateNumber is defined in Helper.hs:9 and used in:
        - Calculator.hs:21 (in add function)
        - Calculator.hs:25 (in subtract function)

        This proves Serena can track function usage across module boundaries.
        """
        # Get references to validateNumber (defined at line 9, 0-indexed = line 8)
        references = language_server.request_references("src/Helper.hs", line=8, column=0)

        # Should find at least: definition in Helper.hs + 2 usages in Calculator.hs
        assert len(references) >= 2, f"Expected at least 2 references to validateNumber (used in add and subtract), got {len(references)}"

        # Verify we have references in Calculator.hs
        reference_paths = [ref["relativePath"] for ref in references]
        calculator_refs = [path for path in reference_paths if "Calculator.hs" in path]

        assert len(calculator_refs) >= 2, (
            f"Expected at least 2 references in Calculator.hs (add and subtract functions), "
            f"got {len(calculator_refs)} references in Calculator.hs"
        )

    @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
    def test_within_file_references_isNegative(self, language_server: SolidLanguageServer):
        """
        Test within-file reference tracking for isNegative function.

        isNegative is defined in Helper.hs:17 and used in Helper.hs:22 (absolute function).
        This proves Serena can track intra-module function calls.
        """
        # isNegative defined at line 17 (0-indexed = line 16)
        references = language_server.request_references("src/Helper.hs", line=16, column=0)

        # Should find: definition + usage in absolute function
        assert len(references) >= 1, f"Expected at least 1 reference to isNegative (used in absolute), got {len(references)}"

        # All references should be in Helper.hs
        reference_paths = [ref["relativePath"] for ref in references]
        assert all("Helper.hs" in path for path in reference_paths), (
            f"All isNegative references should be in Helper.hs, got: {reference_paths}"
        )

    @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
    def test_function_references_from_main(self, language_server: SolidLanguageServer):
        """
        Test that functions used in Main.hs can be traced back to their definitions.

        Main.hs:12 calls 'add' from Calculator module.
        Main.hs:25 calls 'isPositive' from Helper module.
        Main.hs:26 calls 'absolute' from Helper module.

        This proves Serena can track cross-module function calls from executable code.
        """
        # Test 'add' function references (defined in Calculator.hs:20, 0-indexed = line 19)
        add_refs = language_server.request_references("src/Calculator.hs", line=19, column=0)

        # Should find references in Main.hs and possibly Calculator.hs (calculate function uses it)
        assert len(add_refs) >= 1, f"Expected at least 1 reference to 'add', got {len(add_refs)}"

        add_ref_paths = [ref["relativePath"] for ref in add_refs]
        # Should have at least one reference in Main.hs or Calculator.hs
        assert any("Main.hs" in path or "Calculator.hs" in path for path in add_ref_paths), (
            f"Expected 'add' to be referenced in Main.hs or Calculator.hs, got: {add_ref_paths}"
        )

    @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
    def test_multiply_function_usage_in_calculate(self, language_server: SolidLanguageServer):
        """
        Test that multiply function usage is tracked within Calculator module.

        multiply is defined in Calculator.hs:28 and used in:
        - Calculator.hs:41 (in calculate function via pattern matching)
        - Main.hs:20 (via calculate call with "multiply" operator)

        This proves Serena can track function references even when called indirectly.
        """
        # multiply defined at line 28 (0-indexed = line 27)
        multiply_refs = language_server.request_references("src/Calculator.hs", line=27, column=0)

        # Should find at least the usage in calculate function
        assert len(multiply_refs) >= 1, f"Expected at least 1 reference to 'multiply', got {len(multiply_refs)}"

        # Should have reference in Calculator.hs (calculate function)
        multiply_ref_paths = [ref["relativePath"] for ref in multiply_refs]
        assert any("Calculator.hs" in path for path in multiply_ref_paths), (
            f"Expected 'multiply' to be referenced in Calculator.hs, got: {multiply_ref_paths}"
        )

    @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
    def test_data_type_constructor_references(self, language_server: SolidLanguageServer):
        """
        Test that Calculator data type constructor usage is tracked.

        Calculator is defined in Calculator.hs:14 and used in:
        - Main.hs:8 (constructor call: Calculator "TestCalc" 1)
        - Calculator.hs:37 (type signature for calculate function)

        This proves Serena can track data type constructor references.
        """
        # Calculator data type defined at line 14 (0-indexed = line 13)
        calculator_refs = language_server.request_references("src/Calculator.hs", line=13, column=5)

        # Should find usage in Main.hs
        assert len(calculator_refs) >= 1, f"Expected at least 1 reference to Calculator constructor, got {len(calculator_refs)}"

        # Should have at least one reference in Main.hs or Calculator.hs
        calc_ref_paths = [ref["relativePath"] for ref in calculator_refs]
        assert any("Main.hs" in path or "Calculator.hs" in path for path in calc_ref_paths), (
            f"Expected Calculator to be referenced in Main.hs or Calculator.hs, got: {calc_ref_paths}"
        )

    @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
    def test_bare_symbol_names(self, language_server) -> None:
        all_symbols = request_all_symbols(language_server)
        malformed_symbols = []
        for s in all_symbols:
            if s["kind"] == SymbolKind.Module:
                continue
            if has_malformed_name(s):
                malformed_symbols.append(s)
        if malformed_symbols:
            diagnostics = [
                {
                    "formatted": format_symbol_for_assert(sym),
                    "name": sym["name"],
                    "kind": SymbolKind(sym["kind"]).name,
                    "detail": sym.get("detail"),
                }
                for sym in malformed_symbols
            ]
            pytest.fail(f"Found malformed symbols: {diagnostics}", pytrace=False)

    @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
    def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
        assert_file_diagnostics(
            language_server,
            "src/DiagnosticsSample.hs",
            (),
            min_count=1,
        )
