"""Tests for StrictValue mode behavior."""
from typing import Any, cast

import pytest

from crystflux.v1 import Crystallizer, StrictValue, VOID
from crystflux.v1.core.value_array import StrictArrayValue


def test_strict_navigation_and_errors(tao_data: dict[str, Any]) -> None:
    """Test strict navigation with proper data and error cases."""
    laozi = StrictValue(tao_data["laozi"])
    assert laozi.get("author").expected_str() == "老子"

    with pytest.raises(KeyError):
        laozi.get("missing")
    with pytest.raises(TypeError):
        laozi.at(0)

    fragments = StrictValue(tao_data["anthology"]["fragments"])
    assert fragments.at(0).get("source").expected_str() == "道徳経"
    with pytest.raises(IndexError):
        fragments.at(10)
    with pytest.raises(TypeError):
        fragments.get("source")


def test_strict_void_helper_returns_void() -> None:
    """Test that strict scalar's _void() returns VOID."""
    strict_scalar = StrictValue(1)
    assert strict_scalar._void() is VOID  # type: ignore[attr-defined]


def test_freeze_value_enforces_immutability() -> None:
    """Test that freeze_value prevents mutation of original data."""
    original: dict[str, Any] = {"a": {"b": 1}, "list": [1, {"c": 2}]}
    latent = Crystallizer.latent(original)

    obj = cast(dict[str, Any], original["a"])
    obj["b"] = 99
    lst = cast(list[Any], original["list"])
    nested = cast(dict[str, Any], lst[1])
    nested["c"] = 42

    assert latent.get("a").get("b").expected_int() == 1
    assert latent.get("list").at(1).get("c").expected_int() == 2


def test_as_str_array_and_expected(tao_data: dict[str, Any]) -> None:
    """Test as_str_array and expected_str_array for strict mode."""
    works = tao_data["laozi"]["works"]
    fragments = tao_data["anthology"]["fragments"]

    strict = StrictValue(works)
    assert strict.as_str_array() == tuple(works)
    assert strict.expected_str_array() == tuple(works)
    assert StrictValue(fragments).as_str_array() == ()
    with pytest.raises(TypeError):
        StrictValue(fragments).expected_str_array()


def test_as_int_array_and_expected() -> None:
    """Test as_int_array and expected_int_array for strict mode."""
    strict = StrictValue([1, 2, 3])
    assert strict.as_int_array() == (1, 2, 3)
    assert strict.expected_int_array() == (1, 2, 3)

    mixed = StrictValue([1, True, 2])
    assert mixed.as_int_array() == (1, 2)
    with pytest.raises(TypeError):
        mixed.expected_int_array()


def test_as_float_array_and_expected() -> None:
    """Test as_float_array and expected_float_array for strict mode."""
    strict = StrictValue([1.1, 2.2, 3.3])
    assert strict.as_float_array() == (1.1, 2.2, 3.3)
    assert strict.expected_float_array() == (1.1, 2.2, 3.3)

    mixed = StrictValue([1.1, True, 2.2, "x", 3.3])
    assert mixed.as_float_array() == (1.1, 2.2, 3.3)
    with pytest.raises(TypeError):
        mixed.expected_float_array()


def test_as_float_array_minimal_edge_cases() -> None:
    """Test edge cases for as_float_array."""
    empty = StrictValue([])
    assert empty.as_float_array() == ()
    assert empty.expected_float_array() == ()

    single = StrictValue([1.1])
    assert single.as_float_array() == (1.1,)
    assert single.expected_float_array() == (1.1,)

    mixed = StrictValue([1.1, 1, True, "1.1", None, 2.2])
    assert mixed.as_float_array() == (1.1, 2.2)
    with pytest.raises(TypeError):
        mixed.expected_float_array()

    scalar_float = StrictValue(3.14)
    assert scalar_float.as_float() == 3.14
    assert scalar_float.as_bool() is None

    scalar_bool = StrictValue(True)
    assert scalar_bool.as_bool() is True
    assert scalar_bool.as_float() is None


def test_as_scalars_collects_nested_scalars() -> None:
    """Test as_scalars for strict mode."""
    strict = StrictValue([1, True, "x", None, [2.5, {"a": 1}, (3, False)]])
    assert strict.as_scalars() == (1, True, "x", None, 2.5, 3, False)

    empty = StrictValue([])
    assert empty.as_scalars() == ()


def test_handle_non_array_split_modes() -> None:
    """Test that strict mode raises TypeError for split operations on non-arrays."""
    with pytest.raises(TypeError, match="Cannot treat non-array as array: mappingproxy"):
        StrictValue({"key": "value"}).first_rest()
    with pytest.raises(TypeError, match="Cannot treat non-array as array: mappingproxy"):
        StrictValue({"key": "value"}).rest_last()


def test_array_operations_type_errors_and_void_fallbacks() -> None:
    """Test array operations with type errors for strict mode."""
    strict_non_array = StrictArrayValue({"key": "value"})

    with pytest.raises(TypeError, match="Cannot operate on non-array type: mappingproxy"):
        strict_non_array.map(lambda v: v)

    with pytest.raises(TypeError, match="Cannot operate on non-array type: mappingproxy"):
        strict_non_array.filter(lambda v: True)


def test_internal_array_helper_methods() -> None:
    """Test internal array helper methods for strict mode."""
    strict_array = StrictArrayValue([1, 2, 3])
    strict_non_array = StrictArrayValue({"key": "value"})

    result = strict_array._require_array_or_void()  # type: ignore[reportPrivateUsage]
    assert result == (1, 2, 3)

    with pytest.raises(TypeError, match="Cannot operate on non-array type: mappingproxy"):
        strict_non_array._require_array_or_void()  # type: ignore[reportPrivateUsage]

    with pytest.raises(
        TypeError, match="Cannot split array parts from non-array type: mappingproxy"
    ):
        strict_non_array._handle_non_array_split()  # type: ignore[reportPrivateUsage]


def test_array_split_operations_direct_coverage() -> None:
    """Test array split operations directly for strict mode."""
    strict_non_array = StrictArrayValue({"key": "value"})

    with pytest.raises(
        TypeError, match="Cannot split array parts from non-array type: mappingproxy"
    ):
        strict_non_array.first_rest()

    with pytest.raises(
        TypeError, match="Cannot split array parts from non-array type: mappingproxy"
    ):
        strict_non_array.rest_last()
