"""Tests for Field type casting and casted_value methods."""

import pytest

from therismos import Eq, F, Field


class TestFieldCasting:
    """Test Field type casting functionality."""

    def test_cast_no_type(self) -> None:
        """Test casting when no type is specified."""
        field = F("name")
        assert field.cast("Alice") == "Alice"
        assert field.cast(123) == 123

    @pytest.mark.parametrize(
        "type_,value,expected",
        [
            (int, "42", 42),
            (int, 42.7, 42),
            (str, 123, "123"),
            (float, "3.14", 3.14),
            (bool, 1, True),
            (bool, 0, False),
        ],
    )
    def test_cast_with_type(self, type_: type, value: object, expected: object) -> None:
        """Test successful type casting."""
        field = Field("value", type_)
        assert field.cast(value) == expected

    @pytest.mark.parametrize(
        "type_,value",
        [
            (int, "not_a_number"),
            (float, "invalid"),
        ],
    )
    def test_cast_failure(self, type_: type, value: str) -> None:
        """Test that casting failures raise informative errors."""
        field = Field("value", type_)
        with pytest.raises((ValueError, TypeError)) as exc_info:
            field.cast(value)
        # Check that the error message is informative
        assert "Failed to cast" in str(exc_info.value)
        assert repr(value) in str(exc_info.value)

    def test_cast_with_custom_function(self) -> None:
        """Test casting with a custom function."""

        def to_upper(s: object) -> str:
            return str(s).upper()

        field = Field("name", to_upper)
        assert field.cast("alice") == "ALICE"
        assert field.cast(123) == "123"


class TestCastedValueMethods:
    """Test casted_value and casted_values methods on expressions."""

    def test_eq_casted_value(self) -> None:
        """Test casted_value on Eq expression."""
        field = F("age", int)
        expr = field == "42"
        assert expr.casted_value() == 42

    def test_ne_casted_value(self) -> None:
        """Test casted_value on Ne expression."""
        field = F("count", int)
        expr = field != "10"
        assert expr.casted_value() == 10

    @pytest.mark.parametrize(
        "operation,value",
        [
            (lambda f: f < "100", 100),
            (lambda f: f <= "100", 100),
            (lambda f: f > "100", 100),
            (lambda f: f >= "100", 100),
        ],
    )
    def test_comparison_casted_value(self, operation: object, value: int) -> None:
        """Test casted_value on comparison expressions."""
        field = F("price", int)
        expr = operation(field)
        assert expr.casted_value() == value  # type: ignore

    def test_in_casted_values(self) -> None:
        """Test casted_values on In expression."""
        field = F("id", int)
        expr = field.is_in("1", "2", "3")
        assert expr.casted_values() == (1, 2, 3)

    def test_one_of_casted_values(self) -> None:
        """Test casted_values on is_one_of expression."""
        field = F("score", float)
        expr = field.is_one_of(["1.5", "2.7", "3.14"])
        assert expr.casted_values() == (1.5, 2.7, 3.14)

    def test_casted_value_preserves_original(self) -> None:
        """Test that casted_value doesn't modify the original value."""
        field = F("age", int)
        expr = Eq(field, "42")
        assert expr.value == "42"  # Original unchanged
        assert expr.casted_value() == 42  # Casted version


class TestCustomCastFunctions:
    """Test custom cast functions on fields."""

    def test_custom_list_parser(self) -> None:
        """Test a custom cast function that parses comma-separated values."""

        def parse_csv(value: object) -> list[str]:
            if isinstance(value, list):
                return value
            return str(value).split(",")

        field = Field("tags", parse_csv)
        assert field.cast("a,b,c") == ["a", "b", "c"]
        assert field.cast(["x", "y"]) == ["x", "y"]

    def test_custom_strip_and_lower(self) -> None:
        """Test a custom cast function that strips and lowercases."""

        def normalize(value: object) -> str:
            return str(value).strip().lower()

        field = Field("email", normalize)
        assert field.cast("  Alice@Example.COM  ") == "alice@example.com"

    def test_custom_cast_in_expression(self) -> None:
        """Test custom cast function in an expression."""

        def double(value: object) -> int:
            return int(value) * 2

        field = Field("multiplier", double)
        expr = field == "5"
        assert expr.casted_value() == 10
