"""Tests for validate_inputs and empty string sanitization."""

from typing import Any

from pydantic import BaseModel

from flowire_sdk import BaseNodeOutput, NodeMetadata
from flowire_sdk.node_base import BaseNode


class TestEmptyStringSanitization:
    """Tests for _sanitize_empty_strings and _type_accepts_string."""

    def setup_method(self):
        """Create node instances with various input schemas for testing."""

        # Schema with various field types
        class MixedInput(BaseModel):
            string_field: str = ""
            optional_string: str | None = None
            dict_field: dict[str, str] | None = None
            list_field: list[str] | None = None
            int_field: int | None = None
            bool_field: bool | None = None
            any_field: Any = None

        class TestOutput(BaseNodeOutput):
            result: str = ""

        class MixedNode(BaseNode):
            input_schema = MixedInput
            output_schema = TestOutput
            metadata = NodeMetadata(name="Mixed", description="Node with mixed types")

            async def execute_logic(self, validated_inputs, context):
                return TestOutput(result="test")

        self.mixed_node = MixedNode()
        self.MixedInput = MixedInput

    def test_type_accepts_string_for_str(self):
        """str type should accept strings."""
        assert self.mixed_node._type_accepts_string(str) is True

    def test_type_accepts_string_for_optional_str(self):
        """str | None should accept strings."""
        assert self.mixed_node._type_accepts_string(str | None) is True

    def test_type_accepts_string_for_dict(self):
        """dict type should not accept strings."""
        assert self.mixed_node._type_accepts_string(dict) is False

    def test_type_accepts_string_for_optional_dict(self):
        """dict | None should not accept strings."""
        assert self.mixed_node._type_accepts_string(dict[str, str] | None) is False

    def test_type_accepts_string_for_list(self):
        """list type should not accept strings."""
        assert self.mixed_node._type_accepts_string(list) is False

    def test_type_accepts_string_for_optional_list(self):
        """list | None should not accept strings."""
        assert self.mixed_node._type_accepts_string(list[str] | None) is False

    def test_type_accepts_string_for_int(self):
        """int type should not accept strings."""
        assert self.mixed_node._type_accepts_string(int) is False

    def test_type_accepts_string_for_optional_int(self):
        """int | None should not accept strings."""
        assert self.mixed_node._type_accepts_string(int | None) is False

    def test_type_accepts_string_for_bool(self):
        """bool type should not accept strings."""
        assert self.mixed_node._type_accepts_string(bool) is False

    def test_type_accepts_string_for_any(self):
        """Any type should accept strings (technically accepts anything)."""
        assert self.mixed_node._type_accepts_string(Any) is True

    def test_sanitize_leaves_string_field_alone(self):
        """Empty string in string field should remain as empty string."""
        inputs = {"string_field": ""}
        result = self.mixed_node._sanitize_empty_strings(inputs, self.MixedInput)
        assert result["string_field"] == ""

    def test_sanitize_leaves_optional_string_alone(self):
        """Empty string in optional string field should remain as empty string."""
        inputs = {"optional_string": ""}
        result = self.mixed_node._sanitize_empty_strings(inputs, self.MixedInput)
        assert result["optional_string"] == ""

    def test_sanitize_converts_dict_field(self):
        """Empty string in dict field should be converted to None."""
        inputs = {"dict_field": ""}
        result = self.mixed_node._sanitize_empty_strings(inputs, self.MixedInput)
        assert result["dict_field"] is None

    def test_sanitize_converts_list_field(self):
        """Empty string in list field should be converted to None."""
        inputs = {"list_field": ""}
        result = self.mixed_node._sanitize_empty_strings(inputs, self.MixedInput)
        assert result["list_field"] is None

    def test_sanitize_converts_int_field(self):
        """Empty string in int field should be converted to None."""
        inputs = {"int_field": ""}
        result = self.mixed_node._sanitize_empty_strings(inputs, self.MixedInput)
        assert result["int_field"] is None

    def test_sanitize_converts_bool_field(self):
        """Empty string in bool field should be converted to None."""
        inputs = {"bool_field": ""}
        result = self.mixed_node._sanitize_empty_strings(inputs, self.MixedInput)
        assert result["bool_field"] is None

    def test_sanitize_leaves_any_field_alone(self):
        """Empty string in Any field should remain as empty string."""
        inputs = {"any_field": ""}
        result = self.mixed_node._sanitize_empty_strings(inputs, self.MixedInput)
        assert result["any_field"] == ""

    def test_sanitize_preserves_valid_dict(self):
        """Valid dict value should be preserved."""
        inputs = {"dict_field": {"key": "value"}}
        result = self.mixed_node._sanitize_empty_strings(inputs, self.MixedInput)
        assert result["dict_field"] == {"key": "value"}

    def test_sanitize_preserves_none(self):
        """None values should be preserved."""
        inputs = {"dict_field": None}
        result = self.mixed_node._sanitize_empty_strings(inputs, self.MixedInput)
        assert result["dict_field"] is None

    def test_sanitize_preserves_non_empty_string(self):
        """Non-empty strings in non-string fields should be preserved (Pydantic will validate)."""
        inputs = {"dict_field": "not_empty"}
        result = self.mixed_node._sanitize_empty_strings(inputs, self.MixedInput)
        assert result["dict_field"] == "not_empty"


class TestValidateInputsIntegration:
    """Integration tests for validate_inputs with empty string handling."""

    def setup_method(self):
        """Create a node similar to HTTPRequestNode for testing."""

        class HTTPLikeInput(BaseModel):
            url: str
            method: str = "GET"
            headers: dict[str, str] | None = None
            params: dict[str, str] | None = None
            body: dict[str, Any] | None = None
            timeout: int = 30

        class HTTPLikeOutput(BaseNodeOutput):
            status_code: int = 200
            body: dict = {}

        class HTTPLikeNode(BaseNode):
            input_schema = HTTPLikeInput
            output_schema = HTTPLikeOutput
            metadata = NodeMetadata(name="HTTP Like", description="HTTP-like node")

            async def execute_logic(self, validated_inputs, context):
                return HTTPLikeOutput(status_code=200, body={})

        self.node = HTTPLikeNode()

    def test_validate_with_empty_params_string(self):
        """Empty string in params should be converted to None and validate successfully."""
        inputs = {
            "url": "https://example.com",
            "method": "GET",
            "params": "",  # This was the original bug
        }
        result = self.node.validate_inputs(inputs)
        assert result["url"] == "https://example.com"
        assert result["params"] is None

    def test_validate_with_empty_headers_string(self):
        """Empty string in headers should be converted to None."""
        inputs = {
            "url": "https://example.com",
            "headers": "",
        }
        result = self.node.validate_inputs(inputs)
        assert result["headers"] is None

    def test_validate_with_empty_body_string(self):
        """Empty string in body should be converted to None."""
        inputs = {
            "url": "https://example.com",
            "body": "",
        }
        result = self.node.validate_inputs(inputs)
        assert result["body"] is None

    def test_validate_with_valid_dict_values(self):
        """Valid dict values should pass through."""
        inputs = {
            "url": "https://example.com",
            "params": {"key": "value"},
            "headers": {"Content-Type": "application/json"},
        }
        result = self.node.validate_inputs(inputs)
        assert result["params"] == {"key": "value"}
        assert result["headers"] == {"Content-Type": "application/json"}

    def test_validate_preserves_string_fields(self):
        """Empty string in actual string field should be preserved (if schema allows)."""
        inputs = {
            "url": "https://example.com",
            "method": "",  # Empty method - Pydantic default will apply
        }
        # This will either use default or validate - the key is it won't crash
        result = self.node.validate_inputs(inputs)
        assert result["url"] == "https://example.com"

    def test_validate_with_multiple_empty_fields(self):
        """Multiple empty string fields should all be converted."""
        inputs = {
            "url": "https://example.com",
            "params": "",
            "headers": "",
            "body": "",
        }
        result = self.node.validate_inputs(inputs)
        assert result["params"] is None
        assert result["headers"] is None
        assert result["body"] is None
