"""Tests for private helper functions in value_projections.py.

These tests cover the core comparison and scalar conversion logic that powers
the It/Projection system. Testing these helpers directly ensures the fundamental
operations are correct and makes refactoring safer.
"""

from __future__ import annotations

import pytest

from crystflux.v1 import Crystallizer
from crystflux.v1.adapters import with_dream_mode
from crystflux.v1.core.value_projections import (  # type: ignore[misc]
    _compare_value,  # type: ignore[misc]
    _scalar_value,  # type: ignore[misc]
    Projection,
)


# ----------------------------------------------------------------------
# Tests for _compare_value() function
# ----------------------------------------------------------------------


class TestCompareValueAllOperators:
    """Test _compare_value with all comparison operators."""

    def test_eq_operator(self) -> None:
        """Test equality comparison (==)."""
        assert _compare_value(5, "eq", 5, strict=False) is True
        assert _compare_value(5, "eq", 3, strict=False) is False
        assert _compare_value(5.0, "eq", 5, strict=False) is True  # int vs float

    def test_ne_operator(self) -> None:
        """Test inequality comparison (!=)."""
        assert _compare_value(5, "ne", 3, strict=False) is True
        assert _compare_value(5, "ne", 5, strict=False) is False

    def test_lt_operator(self) -> None:
        """Test less-than comparison (<)."""
        assert _compare_value(3, "lt", 5, strict=False) is True
        assert _compare_value(5, "lt", 3, strict=False) is False
        assert _compare_value(5, "lt", 5, strict=False) is False

    def test_le_operator(self) -> None:
        """Test less-or-equal comparison (<=)."""
        assert _compare_value(3, "le", 5, strict=False) is True
        assert _compare_value(5, "le", 5, strict=False) is True
        assert _compare_value(5, "le", 3, strict=False) is False

    def test_gt_operator(self) -> None:
        """Test greater-than comparison (>)."""
        assert _compare_value(5, "gt", 3, strict=False) is True
        assert _compare_value(3, "gt", 5, strict=False) is False
        assert _compare_value(5, "gt", 5, strict=False) is False

    def test_ge_operator(self) -> None:
        """Test greater-or-equal comparison (>=)."""
        assert _compare_value(5, "ge", 3, strict=False) is True
        assert _compare_value(5, "ge", 5, strict=False) is True
        assert _compare_value(3, "ge", 5, strict=False) is False


class TestCompareValueWithDifferentTypes:
    """Test _compare_value with int, float, bool types."""

    def test_int_comparisons(self) -> None:
        """Test comparisons with integer values."""
        assert _compare_value(10, "gt", 5, strict=False) is True
        assert _compare_value(-5, "lt", 0, strict=False) is True

    def test_float_comparisons(self) -> None:
        """Test comparisons with float values."""
        assert _compare_value(3.14, "gt", 3.0, strict=False) is True
        assert _compare_value(-2.5, "lt", 0.0, strict=False) is True

    def test_bool_comparisons(self) -> None:
        """Test comparisons with boolean values."""
        assert _compare_value(True, "eq", True, strict=False) is True
        assert _compare_value(False, "ne", True, strict=False) is True
        assert _compare_value(True, "gt", False, strict=False) is True

    def test_int_vs_float_comparisons(self) -> None:
        """Test comparing int with float (should work)."""
        assert _compare_value(5, "eq", 5.0, strict=False) is True
        assert _compare_value(5, "lt", 5.5, strict=False) is True


class TestCompareValueNoneHandling:
    """Test _compare_value with None values."""

    def test_none_in_non_strict_mode(self) -> None:
        """Test None returns False in non-strict mode."""
        # All comparisons with None should return False in non-strict mode
        assert _compare_value(None, "eq", 5, strict=False) is False
        assert _compare_value(None, "ne", 5, strict=False) is False
        assert _compare_value(None, "lt", 5, strict=False) is False
        assert _compare_value(None, "le", 5, strict=False) is False
        assert _compare_value(None, "gt", 5, strict=False) is False
        assert _compare_value(None, "ge", 5, strict=False) is False

    def test_none_in_strict_mode_raises(self) -> None:
        """Test None raises TypeError in strict mode."""
        with pytest.raises(TypeError, match="Expected scalar value, got None"):
            _compare_value(None, "eq", 5, strict=True)

        with pytest.raises(TypeError, match="Expected scalar value, got None"):
            _compare_value(None, "gt", 5, strict=True)


class TestCompareValueTypeErrors:
    """Test _compare_value with incompatible types."""

    def test_string_vs_number_raises_type_error(self) -> None:
        """Test comparing string with number returns False (caught TypeError)."""
        # Python's < raises TypeError for str vs int, which is caught
        result = _compare_value("hello", "lt", 5, strict=False)  # type: ignore[arg-type]
        assert result is False

    def test_unsupported_operator_raises_value_error(self) -> None:
        """Test unsupported operator raises ValueError."""
        with pytest.raises(ValueError, match="Unsupported compare op"):
            _compare_value(5, "invalid", 3, strict=False)  # type: ignore[arg-type]


# ----------------------------------------------------------------------
# Tests for _scalar_value() function (standard form, no mock)
# ----------------------------------------------------------------------


# int conversions
def test_scalar_value_as_int_with_int_strict():
    """Test as_int conversion with int value in strict mode."""
    val = Crystallizer.strict(42)
    result = _scalar_value(val, "int", expected=False)
    assert result == 42


def test_scalar_value_as_int_with_int_latent():
    """Test as_int conversion with int value in latent mode."""
    val = Crystallizer.latent(42)
    result = _scalar_value(val, "int", expected=False)
    assert result == 42


@with_dream_mode
def test_scalar_value_as_int_with_int_dream():
    """Test as_int conversion with int value in dream mode."""
    val = Crystallizer.dream(42)
    result = _scalar_value(val, "int", expected=False)
    assert result == 42


@with_dream_mode
def test_scalar_value_expected_int_with_int_dream():
    """Test expected_int conversion with int value in dream mode."""
    val = Crystallizer.dream(42)
    result = _scalar_value(val, "int", expected=True)
    assert result == 42


def test_scalar_value_expected_int_with_int_strict():
    """Test expected_int conversion with int value in strict mode."""
    val = Crystallizer.strict(42)
    result = _scalar_value(val, "int", expected=True)
    assert result == 42


def test_scalar_value_expected_int_with_int_latent():
    """Test expected_int conversion with int value in latent mode."""
    val = Crystallizer.latent(42)
    result = _scalar_value(val, "int", expected=True)
    assert result == 42


# float conversions
def test_scalar_value_as_float_with_float_strict():
    """Test as_float conversion with float value in strict mode."""
    val = Crystallizer.strict(3.14)
    result = _scalar_value(val, "float", expected=False)
    assert result == 3.14
    assert isinstance(result, float)


def test_scalar_value_as_float_with_float_latent():
    """Test as_float conversion with float value in latent mode."""
    val = Crystallizer.latent(3.14)
    result = _scalar_value(val, "float", expected=False)
    assert result == 3.14
    assert isinstance(result, float)


@with_dream_mode
def test_scalar_value_as_float_with_float_dream():
    """Test as_float conversion with float value in dream mode."""
    val = Crystallizer.dream(3.14)
    result = _scalar_value(val, "float", expected=False)
    assert result == 3.14
    assert isinstance(result, float)


def test_scalar_value_expected_float_with_float_strict():
    """Test expected_float conversion with float value in strict mode."""
    val = Crystallizer.strict(2.71)
    result = _scalar_value(val, "float", expected=True)
    assert result == 2.71


# bool conversions
def test_scalar_value_as_bool_with_true_strict():
    """Test as_bool conversion with True value in strict mode."""
    val = Crystallizer.strict(True)
    result = _scalar_value(val, "bool", expected=False)
    assert result is True


def test_scalar_value_as_bool_with_true_latent():
    """Test as_bool conversion with True value in latent mode."""
    val = Crystallizer.latent(True)
    result = _scalar_value(val, "bool", expected=False)
    assert result is True


@with_dream_mode
def test_scalar_value_as_bool_with_true_dream():
    """Test as_bool conversion with True value in dream mode."""
    val = Crystallizer.dream(True)
    result = _scalar_value(val, "bool", expected=False)
    assert result is True


def test_scalar_value_expected_bool_with_true_strict():
    """Test expected_bool conversion with True value in strict mode."""
    val = Crystallizer.strict(True)
    result = _scalar_value(val, "bool", expected=True)
    assert result is True


def test_scalar_value_expected_bool_with_true_latent():
    """Test expected_bool conversion with True value in latent mode."""
    val = Crystallizer.latent(True)
    result = _scalar_value(val, "bool", expected=True)
    assert result is True


@with_dream_mode
def test_scalar_value_expected_bool_with_true_dream():
    """Test expected_bool conversion with True value in dream mode."""
    val = Crystallizer.dream(True)
    result = _scalar_value(val, "bool", expected=True)
    assert result is True


# error cases
def test_scalar_value_as_int_with_none():
    """Test as_int with None returns None."""
    val = Crystallizer.latent(None)
    result = _scalar_value(val, "int", expected=False)
    assert result is None


def test_scalar_value_expected_int_with_none_raises():
    """Test expected_int with None raises TypeError."""
    val = Crystallizer.strict(None)
    with pytest.raises(TypeError, match="Expected.*integer"):
        _scalar_value(val, "int", expected=True)


def test_scalar_value_unsupported_type_raises():
    """Test _scalar_value with unsupported scalar_type raises ValueError."""
    val = Crystallizer.strict(42)
    with pytest.raises(ValueError, match="Unsupported scalar type"):
        _scalar_value(val, "invalid", expected=False)  # type: ignore[arg-type]


# ----------------------------------------------------------------------
# Tests for Projection._append() method
# ----------------------------------------------------------------------


class TestProjectionAppend:
    """Test Projection._append() method."""

    def test_append_creates_new_projection(self) -> None:
        """Test _append creates new Projection with appended operation."""
        from crystflux.v1.core.value_projections import Op

        original = Projection((Op("get", "user"),))
        new_projection = original._append(Op("get", "name"))  # type: ignore[misc]

        # Original should be unchanged
        assert len(original.ops) == 1
        assert original.ops[0].payload == "user"

        # New projection should have both operations
        assert len(new_projection.ops) == 2
        assert new_projection.ops[0].payload == "user"
        assert new_projection.ops[1].payload == "name"

    def test_append_preserves_immutability(self) -> None:
        """Test that _append preserves Projection immutability."""
        from crystflux.v1.core.value_projections import Op

        original = Projection()
        first_append = original._append(Op("get", "a"))  # type: ignore[misc]
        second_append = first_append._append(Op("get", "b"))  # type: ignore[misc]

        # Each step should create a new Projection
        assert len(original.ops) == 0
        assert len(first_append.ops) == 1
        assert len(second_append.ops) == 2

    def test_append_get_operation(self) -> None:
        """Test appending get operation."""
        from crystflux.v1.core.value_projections import Op

        projection = Projection()
        new_projection = projection._append(Op("get", "field"))  # type: ignore[misc]

        assert len(new_projection.ops) == 1
        assert new_projection.ops[0].tag == "get"
        assert new_projection.ops[0].payload == "field"

    def test_append_at_operation(self) -> None:
        """Test appending at operation."""
        from crystflux.v1.core.value_projections import Op

        projection = Projection()
        new_projection = projection._append(Op("at", 0))  # type: ignore[misc]

        assert len(new_projection.ops) == 1
        assert new_projection.ops[0].tag == "at"
        assert new_projection.ops[0].payload == 0
