"""Unit tests for the value parser module."""

import math

import pytest

from godotiq.parsers.value_parser import decompose_transform3d, parse_value


class TestScalarParsing:
    """Tests for bool, int, float, null, and string parsing."""

    def test_parse_true(self) -> None:
        assert parse_value("true") is True

    def test_parse_false(self) -> None:
        assert parse_value("false") is False

    def test_parse_null(self) -> None:
        assert parse_value("null") is None

    def test_parse_int(self) -> None:
        result = parse_value("42")
        assert result == 42
        assert isinstance(result, int)

    def test_parse_float(self) -> None:
        result = parse_value("3.14")
        assert result == pytest.approx(3.14)
        assert isinstance(result, float)

    def test_parse_scientific_notation(self) -> None:
        result = parse_value("-3.059797e-08")
        assert isinstance(result, float)
        assert abs(result) < 1e-7

    def test_parse_string(self) -> None:
        assert parse_value('"hello world"') == "hello world"


class TestVectorMathTypes:
    """Tests for Vector2, Vector3, Color, Rect2, AABB, Quaternion."""

    def test_parse_vector2(self) -> None:
        assert parse_value("Vector2(1.5, 2.0)") == (1.5, 2.0)

    def test_parse_vector3(self) -> None:
        result = parse_value("Vector3(1, 2, 3)")
        assert result == (1.0, 2.0, 3.0)

    def test_parse_vector3i(self) -> None:
        result = parse_value("Vector3i(1, 2, 3)")
        assert result == (1, 2, 3)
        assert all(isinstance(v, int) for v in result)

    def test_parse_color(self) -> None:
        assert parse_value("Color(0.5, 0.3, 0.1, 1.0)") == pytest.approx(
            (0.5, 0.3, 0.1, 1.0)
        )

    def test_parse_rect2(self) -> None:
        assert parse_value("Rect2(0, 0, 100, 50)") == (0.0, 0.0, 100.0, 50.0)

    def test_parse_aabb(self) -> None:
        assert parse_value("AABB(-1, -1, -1, 2, 2, 2)") == (
            -1.0, -1.0, -1.0, 2.0, 2.0, 2.0,
        )

    def test_parse_quaternion(self) -> None:
        assert parse_value("Quaternion(0, 0, 0, 1)") == (0.0, 0.0, 0.0, 1.0)


class TestReferenceTypes:
    """Tests for ExtResource, SubResource, NodePath, StringName."""

    def test_parse_ext_resource(self) -> None:
        result = parse_value('ExtResource("1_abc")')
        assert result == {"type": "ext_resource", "id": "1_abc"}

    def test_parse_sub_resource(self) -> None:
        result = parse_value('SubResource("BoxShape3D_1ufgv")')
        assert result == {"type": "sub_resource", "id": "BoxShape3D_1ufgv"}

    def test_parse_node_path(self) -> None:
        assert parse_value('NodePath("Path/To/Node")') == "Path/To/Node"

    def test_parse_string_name(self) -> None:
        assert parse_value('StringName(&"signal_name")') == "signal_name"


class TestPackedArrays:
    """Tests for PackedByteArray, PackedFloat32Array stored as raw strings."""

    def test_packed_byte_array(self) -> None:
        raw = 'PackedByteArray("AAAB...")'
        assert parse_value(raw) == raw

    def test_packed_float32_array(self) -> None:
        raw = "PackedFloat32Array(1, 2, 3)"
        assert parse_value(raw) == raw


class TestArraysAndDictionaries:
    """Tests for array and dictionary parsing."""

    def test_parse_string_array(self) -> None:
        assert parse_value('["enemy", "damageable"]') == ["enemy", "damageable"]

    def test_parse_int_array(self) -> None:
        assert parse_value("[1, 2, 3]") == [1, 2, 3]

    def test_parse_dict(self) -> None:
        result = parse_value('{"key": "value"}')
        assert isinstance(result, dict)
        assert result["key"] == "value"


class TestTransform3DDecomposition:
    """Tests for Transform3D matrix decomposition."""

    def test_identity_transform(self) -> None:
        values = (1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
        result = decompose_transform3d(values)
        assert result["position"] == (0.0, 0.0, 0.0)
        assert result["rotation"] == (0.0, 0.0, 0.0)
        assert result["scale"] == pytest.approx((1.0, 1.0, 1.0))

    def test_translation_only(self) -> None:
        values = (1, 0, 0, 0, 1, 0, 0, 0, 1, -36.117, 0, -1.947)
        result = decompose_transform3d(values)
        assert result["position"] == pytest.approx((-36.117, 0.0, -1.947))
        assert result["rotation"] == (0.0, 0.0, 0.0)
        assert result["scale"] == pytest.approx((1.0, 1.0, 1.0))

    def test_uniform_scale(self) -> None:
        values = (0.35, 0, 0, 0, 0.35, 0, 0, 0, 0.35, 0, 0.326, 0)
        result = decompose_transform3d(values)
        assert result["scale"] == pytest.approx((0.35, 0.35, 0.35))

    def test_non_uniform_scale(self) -> None:
        values = (1, 0, 0, 0, 1.25, 0, 0, 0, 1, -37.617, 0, -9.447)
        result = decompose_transform3d(values)
        assert result["scale"] == pytest.approx((1.0, 1.25, 1.0))
        assert result["rotation"] == (0.0, 0.0, 0.0)

    def test_180_degree_y_rotation_with_scale(self) -> None:
        values = (-1.1999992, 0, 0.0013823411, 0, 1.2, 0, -0.0013823411, 0, -1.1999992, -3.554, -2.853, -0.708)
        result = decompose_transform3d(values)
        assert abs(result["rotation"][1]) == pytest.approx(math.pi, abs=0.01)
        assert result["scale"] == pytest.approx((1.2, 1.2, 1.2), abs=0.01)

    def test_scientific_notation_cleanup(self) -> None:
        values = (-3.059797e-08, 0, 0.7, 0, 0.7, 0, -0.7, 0, -3.059797e-08, -36.7, 0.687, 1.507)
        result = decompose_transform3d(values)
        assert result["scale"] == pytest.approx((0.7, 0.7, 0.7), abs=0.001)
        # Rotation Y is -pi/2 (the basis columns encode a -90° Y rotation)
        assert result["rotation"][1] == pytest.approx(-math.pi / 2, abs=0.01)

    def test_90_degree_rotation(self) -> None:
        values = (-4.371139e-08, 0, -1, 0, 1.25, 0, 1, 0, -4.371139e-08, -37.617, 0, -8.447)
        result = decompose_transform3d(values)
        # This basis encodes a +pi/2 Y rotation with Y scale 1.25
        assert result["rotation"][1] == pytest.approx(math.pi / 2, abs=0.01)
        assert result["scale"] == pytest.approx((1.0, 1.25, 1.0), abs=0.01)

    def test_negative_determinant(self) -> None:
        values = (-1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
        result = decompose_transform3d(values)
        assert result["scale"][0] < 0

    def test_decompose_transform3d_has_raw(self) -> None:
        """Verify that decompose_transform3d() returns the original 12-float tuple under 'raw'."""
        values = (1, 0, 0, 0, 1, 0, 0, 0, 1, -36.117, 0, -1.947)
        result = decompose_transform3d(values)
        assert "raw" in result
        assert result["raw"] == values

    def test_parse_value_transform3d_has_raw(self) -> None:
        """Verify that parsing a Transform3D string includes the raw key."""
        result = parse_value("Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 0)")
        assert isinstance(result, dict)
        assert "raw" in result
        assert result["raw"] == (1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 5.0, 0.0, 0.0)


class TestEdgeCases:
    """Edge cases for value parser."""

    def test_truefalse_not_bool(self) -> None:
        result = parse_value("truefalse")
        assert result != True  # noqa: E712
        assert result != False  # noqa: E712

    def test_empty_string(self) -> None:
        assert parse_value("") == ""
