"""Tests for godotiq_animation_info tool."""

from __future__ import annotations

import pytest

from godotiq.tools.animation import _build_animation_info, _extract_code_animations


class TestAnimationInfoInlineScene:
    """Test 1: animated_character.tscn has 4 animations with correct metadata."""

    def test_animation_info_inline_scene(self, anim_session):
        """animated_character.tscn should return 4 animations via AnimationPlayer."""
        result = _build_animation_info(anim_session, "res://scenes/animated_character.tscn")
        assert "error" not in result
        players = result["animation_players"]
        assert len(players) == 1
        anims = players[0]["animations"]
        names = {a["name"] for a in anims}
        assert names == {"idle", "walk", "attack", "idle_ground"}

        # Verify idle metadata
        idle = next(a for a in anims if a["name"] == "idle")
        assert idle["length"] == 2.0
        assert idle["loop_mode"] == "linear"
        assert idle["track_count"] == 4
        # Normal detail (default) should exclude track_paths
        assert "track_paths" not in idle


class TestAnimationInfoDetailBrief:
    """Test 2: brief detail returns names only, no track counts."""

    def test_animation_info_detail_brief(self, anim_session):
        """Brief detail should return animation names only."""
        result = _build_animation_info(
            anim_session, "res://scenes/animated_character.tscn", detail="brief"
        )
        anims = result["animation_players"][0]["animations"]
        assert len(anims) == 4
        for a in anims:
            assert "name" in a
            assert "track_count" not in a
            assert "length" not in a


class TestAnimationInfoDetailFull:
    """Test 3: full detail includes track_paths."""

    def test_animation_info_detail_full(self, anim_session):
        """Full detail should include track_paths."""
        result = _build_animation_info(
            anim_session, "res://scenes/animated_character.tscn", detail="full"
        )
        anims = result["animation_players"][0]["animations"]
        idle = next(a for a in anims if a["name"] == "idle")
        assert "track_paths" in idle
        assert len(idle["track_paths"]) > 0


class TestAnimationInfoCodeAnimations:
    """Test 4: character.gd play_calls, ANIM_MAP, LOOPING_ANIMS detected."""

    def test_animation_info_code_animations(self, anim_session):
        """Code animations from character.gd should be detected."""
        result = _build_animation_info(anim_session, "res://scenes/animated_character.tscn")
        code = result["code_animations"]
        assert code  # not empty
        assert "idle" in code["play_calls"]
        assert "attack" in code["play_calls"]
        assert "special_move" in code["play_calls"]
        assert len(code["anim_maps"]) > 0
        assert len(code["looping_lists"]) > 0


class TestAnimationInfoCodeOnlyGlb:
    """Test 5: glb_animated_entity.gd code_animations with NlaTrack entries."""

    def test_animation_info_code_only_glb(self, anim_session):
        """GLB entity scene should have code animations with NlaTrack references."""
        result = _build_animation_info(anim_session, "res://scenes/glb_entity.tscn")
        code = result["code_animations"]
        assert code
        assert len(code["anim_maps"]) > 0
        entries = code["anim_maps"][0]["entries"]
        assert any("NlaTrack" in v for v in entries.values())


class TestAnimationInfoAnimationTree:
    """Test 6: animated_scene_tree.tscn has 2 states, 2 transitions."""

    def test_animation_info_animation_tree(self, anim_session):
        """AnimationTree scene should expose states and transitions."""
        result = _build_animation_info(anim_session, "res://scenes/animated_scene_tree.tscn")
        trees = result["animation_trees"]
        assert len(trees) == 1
        tree = trees[0]
        assert set(tree["states"]) == {"idle", "walk"}
        assert len(tree["transitions"]) == 2
        pairs = {(t["from"], t["to"]) for t in tree["transitions"]}
        assert ("idle", "walk") in pairs
        assert ("walk", "idle") in pairs


class TestAnimationInfoNoAnimations:
    """Test 7: no_animation_scene.tscn returns empty results."""

    def test_animation_info_no_animations(self, anim_session):
        """Scene with no animations should return empty lists and zero counts."""
        result = _build_animation_info(anim_session, "res://scenes/no_animation_scene.tscn")
        assert result["animation_players"] == []
        assert result["animation_trees"] == []
        assert result["summary"]["total_animations"] == 0
        assert result["summary"]["total_players"] == 0
        assert result["summary"]["total_trees"] == 0


class TestAnimationInfoNodeFilter:
    """Test 8: node filter correctly filters to matching player."""

    def test_animation_info_node_filter(self, anim_session):
        """Filtering by node='AnimationPlayer' should return that player."""
        result = _build_animation_info(
            anim_session, "res://scenes/animated_character.tscn", node="AnimationPlayer"
        )
        assert len(result["animation_players"]) == 1
        assert result["animation_players"][0]["name"] == "AnimationPlayer"


class TestAnimationInfoNodeFilterNotFound:
    """Test 9: non-existent node returns error with alternatives."""

    def test_animation_info_node_filter_not_found(self, anim_session):
        """Filtering by non-existent node should return error with alternatives."""
        result = _build_animation_info(
            anim_session, "res://scenes/animated_character.tscn", node="NonExistent"
        )
        assert "error" in result
        assert "alternatives" in result
        assert "AnimationPlayer" in result["alternatives"]


class TestAnimationInfoSummaryCounts:
    """Test 10: summary counts unique animation names correctly."""

    def test_animation_info_summary_counts(self, anim_session):
        """Summary should count unique animations across inline + code sources."""
        result = _build_animation_info(anim_session, "res://scenes/animated_character.tscn")
        summary = result["summary"]
        # 4 inline + "special_move" from code = 5 unique
        assert summary["total_animations"] == 5
        assert summary["total_players"] == 1
        assert summary["inline_count"] == 4
        assert summary["code_only_count"] == 1
        assert "tscn_inline" in summary["sources"]
        assert "code" in summary["sources"]


class TestAnimationInfoMissingScene:
    """Test 11: non-existent scene path returns error."""

    def test_animation_info_missing_scene(self, anim_session):
        """Non-existent scene should return error dict with scene path."""
        result = _build_animation_info(anim_session, "res://scenes/does_not_exist.tscn")
        assert "error" in result
        assert result["scene"] == "res://scenes/does_not_exist.tscn"


class TestAnimationInfoCodePlayDetection:
    """Test 12: unit test _extract_code_animations with synthetic input."""

    def test_animation_info_code_play_detection(self):
        """Direct unit test for play/queue/travel detection."""
        code = '''
var anim_player: AnimationPlayer
var tree: AnimationTree

func _ready():
    anim_player.play("idle")
    anim_player.play(&"walk")
    anim_player.queue("attack")
    tree.get("parameters/playback").travel("run_state")
'''
        result = _extract_code_animations(code)
        assert "idle" in result["play_calls"]
        assert "walk" in result["play_calls"]
        assert "attack" in result["play_calls"]
        assert "run_state" in result["travel_calls"]
        assert result["has_anim_player"] is True
        assert result["has_anim_tree"] is True
