"""Unit tests for the TSCN parser — 23 tests covering all parser features."""

import math
from pathlib import Path

import pytest

from godotiq.parsers.tscn_parser import parse_tscn

FIXTURES = Path(__file__).resolve().parent.parent / "fixtures"


class TestHeaderParsing:
    """Header format from worker.tscn and main.tscn."""

    def test_parse_header_format3(self) -> None:
        scene = parse_tscn(str(FIXTURES / "worker.tscn"))
        assert scene.header.format == 3

    def test_parse_header_format4(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        assert scene.header.format == 4


class TestResourceParsing:
    """Ext and sub resource parsing."""

    def test_parse_ext_resources(self) -> None:
        scene = parse_tscn(str(FIXTURES / "fdm_printer.tscn"))
        assert len(scene.ext_resources) == 3
        res = scene.ext_resources["1_iqbk5"]
        assert res.type == "Script"
        assert res.path.endswith("printer.gd")

    def test_parse_sub_resources(self) -> None:
        scene = parse_tscn(str(FIXTURES / "furniture_slot_placeholder.tscn"))
        assert len(scene.sub_resources) == 3
        mat = scene.sub_resources["StandardMaterial3D_placeholder"]
        assert mat.type == "StandardMaterial3D"
        assert isinstance(mat.properties["albedo_color"], tuple)


class TestNodeParsing:
    """Basic node parsing, hierarchy, and deep nesting."""

    def test_parse_nodes_basic(self) -> None:
        scene = parse_tscn(str(FIXTURES / "worker.tscn"))
        assert len(scene.nodes) == 4
        assert scene.root is not None
        assert scene.root.name == "Worker"
        assert scene.root.type == "CharacterBody3D"

    def test_parse_node_hierarchy(self) -> None:
        scene = parse_tscn(str(FIXTURES / "fdm_printer.tscn"))
        assert scene.root is not None
        assert scene.root.name == "FDMPrinter"
        child_names = [c.name for c in scene.root.children]
        assert "StaticBody3D" in child_names
        assert "PrinterModel" in child_names

    def test_parse_deep_nesting(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        node = scene.find_node(
            "Building/LockedZones/PostProcessingZone/PostProcessingFloor/"
            "FloorTile_0_0/floorFull(Clone)2"
        )
        assert node is not None
        assert node.name == "floorFull(Clone)2"


class TestGroups:
    """Single and multiple groups."""

    def test_parse_groups_single(self) -> None:
        scene = parse_tscn(str(FIXTURES / "fdm_printer.tscn"))
        assert scene.root is not None
        assert "interactable" in scene.root.groups

    def test_parse_groups_multiple(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        nodes = scene.get_nodes_by_group("material_store")
        storage_node = next(n for n in nodes if n.name == "filament_storage")
        assert "material_store" in storage_node.groups
        assert "storage_points" in storage_node.groups


class TestTransforms:
    """Transform3D decomposition: identity, scale, rotation, scientific notation."""

    def test_parse_transform_identity_with_translation(self) -> None:
        scene = parse_tscn(str(FIXTURES / "worker.tscn"))
        node = scene.find_node("CollisionShape3D")
        assert node is not None
        assert node.position == pytest.approx((0.0, 0.5, 0.0))
        assert node.rotation == pytest.approx((0.0, 0.0, 0.0))
        assert node.scale == pytest.approx((1.0, 1.0, 1.0))

    def test_parse_transform_uniform_scale(self) -> None:
        scene = parse_tscn(str(FIXTURES / "fdm_printer.tscn"))
        node = scene.find_node("PrinterModel")
        assert node is not None
        assert node.scale == pytest.approx((0.35, 0.35, 0.35))

    def test_parse_transform_nonuniform_scale(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        # Find a node with non-uniform Y=1.25 scale
        found = False
        for n in scene.nodes:
            if n.scale and abs(n.scale[1] - 1.25) < 0.01:
                found = True
                break
        assert found, "No node with Y scale 1.25 found"

    def test_parse_transform_rotation(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        # Find a node with 90-degree Y rotation pattern
        found = False
        for n in scene.nodes:
            if n.rotation and abs(abs(n.rotation[1]) - math.pi / 2) < 0.1:
                found = True
                break
        assert found, "No node with ~90-degree Y rotation found"

    def test_parse_transform_scientific_notation(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        # Nodes with scientific notation values should have clean rotations
        for n in scene.nodes:
            if n.rotation:
                # No rotation component should be an extremely small non-zero value
                for r in n.rotation:
                    assert r == 0.0 or abs(r) > 1e-6, (
                        f"Unclean rotation {r} on node '{n.name}'"
                    )


class TestNodeAttributes:
    """Instance, script, and nodes without explicit type."""

    def test_parse_instance_node(self) -> None:
        scene = parse_tscn(str(FIXTURES / "fdm_printer.tscn"))
        node = scene.find_node("PrinterModel")
        assert node is not None
        assert node.instance == "2_model"

    def test_parse_script_assignment(self) -> None:
        scene = parse_tscn(str(FIXTURES / "worker.tscn"))
        assert scene.root is not None
        assert scene.root.script == "1_qf3b7"

    def test_parse_node_without_type(self) -> None:
        scene = parse_tscn(str(FIXTURES / "fdm_printer.tscn"))
        node = scene.find_node("PrinterModel")
        assert node is not None
        assert node.type == ""


class TestPropertiesAndMetadata:
    """Custom properties, metadata, and value types."""

    def test_parse_custom_properties(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        node = scene.find_node("Building/LockedZones/PostProcessingZone")
        assert node is not None
        assert node.properties["zone_name"] == "Post-Processing"
        assert node.properties["unlock_cost"] == 5000
        assert node.properties["tier_required"] == 2

    def test_parse_metadata(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        node = scene.find_node("RDLabZone/ResearchDesk")
        assert node is not None
        assert node.metadata["station_type"] == "desk"

    def test_parse_value_types(self) -> None:
        scene = parse_tscn(str(FIXTURES / "furniture_slot_placeholder.tscn"))
        mat = scene.sub_resources["StandardMaterial3D_placeholder"]
        assert isinstance(mat.properties["transparency"], int)
        assert isinstance(mat.properties["albedo_color"], tuple)
        assert len(mat.properties["albedo_color"]) == 4
        box_mesh = scene.sub_resources["BoxMesh_placeholder"]
        assert isinstance(box_mesh.properties["size"], tuple)
        assert len(box_mesh.properties["size"]) == 3


class TestEdgeCases:
    """Special names, stress counts."""

    def test_parse_special_node_names(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        node = scene.find_node("@CSGBox3D@106885")
        assert node is not None
        assert node.name == "@CSGBox3D@106885"

    def test_node_count_stress(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        assert len(scene.nodes) == 876

    def test_ext_resource_count_stress(self) -> None:
        scene = parse_tscn(str(FIXTURES / "main.tscn"))
        assert len(scene.ext_resources) == 68
