"""
Tests for the parameter_extractor module.
"""

from __future__ import annotations

import parso

from param_lsp._analyzer.parameter_extractor import (
    extract_boolean_value,
    extract_bounds_from_call,
    extract_default_from_call,
    extract_doc_from_call,
    extract_numeric_value,
    extract_string_value,
    format_default_value,
    get_keyword_arguments,
    is_none_value,
    is_parameter_assignment,
    is_parameter_call,
)


def parse_expression(code: str):
    """Helper to parse a code expression into parso nodes."""
    tree = parso.parse(code)
    # For simple expressions, parso puts them directly under file_input
    # Skip the endmarker and get the actual expression
    for child in tree.children:
        if child.type != "endmarker":
            return child
    return tree.children[0] if tree.children else tree


class TestExtractNumericValue:
    """Test numeric value extraction."""

    def test_extract_integer(self):
        node = parse_expression("42")
        assert extract_numeric_value(node) == 42

    def test_extract_float(self):
        node = parse_expression("3.14")
        assert extract_numeric_value(node) == 3.14

    def test_extract_negative_integer(self):
        node = parse_expression("-5")
        assert extract_numeric_value(node) == -5

    def test_extract_negative_float(self):
        node = parse_expression("-2.5")
        assert extract_numeric_value(node) == -2.5

    def test_extract_scientific_notation(self):
        node = parse_expression("1e3")
        assert extract_numeric_value(node) == 1000.0

    def test_extract_none_value(self):
        node = parse_expression("None")
        assert extract_numeric_value(node) is None

    def test_extract_non_numeric(self):
        node = parse_expression("'hello'")
        assert extract_numeric_value(node) is None


class TestExtractBooleanValue:
    """Test boolean value extraction."""

    def test_extract_true(self):
        node = parse_expression("True")
        assert extract_boolean_value(node) is True

    def test_extract_false(self):
        node = parse_expression("False")
        assert extract_boolean_value(node) is False

    def test_extract_non_boolean(self):
        node = parse_expression("'hello'")
        assert extract_boolean_value(node) is None


class TestExtractStringValue:
    """Test string value extraction."""

    def test_extract_single_quotes(self):
        node = parse_expression("'hello'")
        assert extract_string_value(node) == "hello"

    def test_extract_double_quotes(self):
        node = parse_expression('"world"')
        assert extract_string_value(node) == "world"

    def test_extract_triple_quotes(self):
        node = parse_expression('"""multiline"""')
        assert extract_string_value(node) == "multiline"

    def test_extract_triple_single_quotes(self):
        node = parse_expression("'''another'''")
        assert extract_string_value(node) == "another"

    def test_extract_non_string(self):
        node = parse_expression("42")
        assert extract_string_value(node) is None


class TestIsNoneValue:
    """Test None value detection."""

    def test_is_none_value_true(self):
        node = parse_expression("None")
        assert is_none_value(node) is True

    def test_is_none_value_false(self):
        node = parse_expression("42")
        assert is_none_value(node) is False

    def test_is_none_value_string(self):
        node = parse_expression("'None'")
        assert is_none_value(node) is False


class TestFormatDefaultValue:
    """Test default value formatting."""

    def test_format_number(self):
        node = parse_expression("42")
        result = format_default_value(node)
        assert result == "42"

    def test_format_string(self):
        node = parse_expression("'hello'")
        result = format_default_value(node)
        assert result == "'hello'"

    def test_format_boolean(self):
        node = parse_expression("True")
        result = format_default_value(node)
        assert result == "True"


class TestIsParameterCall:
    """Test parameter call detection."""

    def test_is_param_integer_call(self):
        node = parse_expression("param.Integer(default=5)")
        # This is a simplified test - in practice this function needs the full context
        # For now, just test that it doesn't crash
        result = is_parameter_call(node)
        assert isinstance(result, bool)

    def test_is_regular_function_call(self):
        node = parse_expression("print('hello')")
        result = is_parameter_call(node)
        assert result is False


class TestIsParameterAssignment:
    """Test parameter assignment detection."""

    def test_is_parameter_assignment_true(self):
        code = "width = param.Integer(default=100)"
        tree = parso.parse(code)
        stmt = tree.children[0]
        # This is a simplified test - the function needs more context in practice
        result = is_parameter_assignment(stmt)
        assert isinstance(result, bool)

    def test_is_regular_assignment(self):
        code = "x = 5"
        tree = parso.parse(code)
        stmt = tree.children[0]
        result = is_parameter_assignment(stmt)
        assert result is False


class TestGetKeywordArguments:
    """Test keyword argument extraction."""

    def test_extract_simple_kwargs(self):
        # Test with a simple function call
        node = parse_expression("func(a=1, b=2)")
        kwargs = get_keyword_arguments(node)
        # The function should return a dict, even if empty for this test case
        assert isinstance(kwargs, dict)

    def test_extract_no_kwargs(self):
        node = parse_expression("func(1, 2)")
        kwargs = get_keyword_arguments(node)
        assert isinstance(kwargs, dict)
        assert len(kwargs) == 0


class TestExtractFromCall:
    """Test extraction from parameter calls."""

    def test_extract_bounds_from_call(self):
        node = parse_expression("param.Integer(bounds=(0, 100))")
        # This is a basic test - the function needs proper parameter call structure
        bounds = extract_bounds_from_call(node)
        # Should return None or a tuple
        assert bounds is None or isinstance(bounds, tuple)

    def test_extract_doc_from_call(self):
        node = parse_expression("param.Integer(doc='A parameter')")
        doc = extract_doc_from_call(node)
        assert doc is None or isinstance(doc, str)

    def test_extract_default_from_call(self):
        node = parse_expression("param.Integer(default=42)")
        default = extract_default_from_call(node)
        # Should return a node or None
        assert default is None or hasattr(default, "type")
