"""Tests for godotiq_asset_registry tool.

Sprint 3c RED phase: These tests define expected behavior for the asset registry
tool and will fail until the implementation is complete.

Design Doc: Sprint 3c — Asset Registry
Generated: 2026-03-03 | Budget Used: 7/7 integration, 0/2 E2E
"""

from __future__ import annotations

from godotiq.session import GodotIQSession
from godotiq.tools.assets import _build_asset_registry


class TestAssetRegistryAll:
    # AC: "Asset registry returns total_assets count and by_category breakdown"
    # Behavior: Call registry with no filters -> total_assets and by_category dict
    # @category: core-functionality
    # @dependency: GodotIQSession, ProjectIndex
    # @complexity: medium
    # ROI: 88 | Business Value: 9 | Frequency: 9
    def test_registry_all(self, session: GodotIQSession) -> None:
        """Returns total_assets and by_category counts."""
        result = _build_asset_registry(session)
        # SKELETON: result must include total_assets as int
        assert "total_assets" in result
        assert isinstance(result["total_assets"], int)
        assert result["total_assets"] >= 2  # fixture has printer_standard.glb + unused_model.glb
        # SKELETON: by_category maps category names to counts
        assert "by_category" in result
        assert isinstance(result["by_category"], dict)
        # SKELETON: fixture has .glb files -> "models" category should be present
        assert "model" in result["by_category"] or "models" in result["by_category"]


class TestAssetRegistryCategoryFilter:
    # AC: "Filtering by category returns only assets of that type"
    # Behavior: category="models" -> only model assets in result
    # @category: core-functionality
    # @dependency: GodotIQSession, ProjectIndex
    # @complexity: low
    # ROI: 80 | Business Value: 8 | Frequency: 7
    def test_registry_category_filter(self, session: GodotIQSession) -> None:
        """category='models' only returns model assets."""
        result = _build_asset_registry(session, category="models", detail="full")
        # SKELETON: all returned assets should be model type
        assert "assets" in result
        for asset in result["assets"]:
            assert asset["type"] in ("model", "models")


class TestAssetRegistryUsageCheck:
    # AC: "Registry identifies which assets are used and which are unused"
    # Behavior: Fixture has printer_standard.glb (used) and unused_model.glb (unused)
    # @category: core-functionality
    # @dependency: GodotIQSession, ProjectIndex, resource_usage
    # @complexity: medium
    # ROI: 85 | Business Value: 9 | Frequency: 8
    def test_registry_usage_check(self, session: GodotIQSession) -> None:
        """Finds used and unused assets."""
        result = _build_asset_registry(session)
        # SKELETON: result must report used and unused counts
        assert "used_count" in result or "usage_summary" in result
        # SKELETON: printer_standard.glb is referenced by fdm_printer.tscn -> used
        # SKELETON: unused_model.glb is not referenced by any scene -> unused
        assert "unused_count" in result or "usage_summary" in result


class TestAssetRegistryUnusedList:
    # AC: "Unused assets list contains paths and file sizes"
    # Behavior: unused_list entries have path and size_bytes fields
    # @category: core-functionality
    # @dependency: GodotIQSession, ProjectIndex
    # @complexity: low
    # ROI: 82 | Business Value: 8 | Frequency: 7
    def test_registry_unused_list(self, session: GodotIQSession) -> None:
        """unused_list has paths and sizes."""
        result = _build_asset_registry(session)
        # SKELETON: unused_list should contain unused_model.glb
        assert "unused_list" in result
        assert len(result["unused_list"]) >= 1
        for entry in result["unused_list"]:
            assert "path" in entry
            assert "size" in entry or "size_bytes" in entry
        unused_paths = [e["path"] for e in result["unused_list"]]
        assert any("unused_model" in p for p in unused_paths)


class TestAssetRegistryUnusedCap:
    # AC: "Unused list is capped at 30 entries to prevent token bloat"
    # Behavior: Even with many unused assets, list capped at 30
    # @category: edge-case
    # @dependency: GodotIQSession, ProjectIndex
    # @complexity: low
    # ROI: 55 | Business Value: 5 | Frequency: 3
    def test_registry_unused_cap(self, session: GodotIQSession) -> None:
        """unused_list capped at 30."""
        result = _build_asset_registry(session)
        # SKELETON: regardless of total unused assets, list must be <= 30
        assert "unused_list" in result
        assert len(result["unused_list"]) <= 30


class TestAssetRegistryByDirectory:
    # AC: "Assets can be grouped by directory"
    # Behavior: by_directory groups asset counts under directory paths
    # @category: integration
    # @dependency: GodotIQSession, ProjectIndex
    # @complexity: low
    # ROI: 65 | Business Value: 6 | Frequency: 5
    def test_registry_by_directory(self, session: GodotIQSession) -> None:
        """Groups assets by directory."""
        result = _build_asset_registry(session, detail="full")
        # SKELETON: by_directory maps directory paths to asset counts or lists
        assert "by_directory" in result
        assert isinstance(result["by_directory"], dict)
        # SKELETON: fixture assets are under res://assets/models/printers/
        assert any("models" in d or "printers" in d for d in result["by_directory"])


class TestAssetRegistryRecursiveUsage:
    # AC: "Asset usage tracked recursively through scene instance chains"
    # Behavior: An asset referenced only by a sub-scene should still be "used"
    #           if that sub-scene is transitively instanced from a top-level scene
    # @category: bugfix
    # @dependency: GodotIQSession, ProjectIndex, scene_instances
    # @complexity: medium
    # ROI: 90 | Business Value: 9 | Frequency: 8
    def test_recursive_usage_tracking(self, session: GodotIQSession) -> None:
        """Assets used by instanced sub-scenes are marked as used via recursive chain."""
        # Inject a fake asset that is referenced ONLY by an orphan sub-scene
        # (not directly, but only by a scene that IS instanced).
        # Also inject a sub-scene that references it but is NOT instanced by anyone.
        original_assets = dict(session._index.assets)
        original_usage = dict(session._index.resource_usage)
        original_instances = dict(session._index.scene_instances)
        try:
            # orphan_sub.tscn is NOT instanced by any scene
            session._index.assets["res://assets/models/orphan_only.glb"] = {
                "type": "model",
                "size_bytes": 100,
                "used_by": ["res://scenes/orphan_sub.tscn"],
            }
            session._index.resource_usage["res://assets/models/orphan_only.glb"] = [
                "res://scenes/orphan_sub.tscn"
            ]
            # orphan_sub.tscn is not in scene_instances values anywhere
            # (it's never instanced by another scene)

            result = _build_asset_registry(session, detail="full")
            orphan_assets = [
                a for a in result["assets"]
                if "orphan_only" in a["path"]
            ]
            assert len(orphan_assets) == 1
            # orphan_only.glb is only referenced by orphan_sub.tscn which is
            # NOT instanced by any other scene. With recursive tracking from
            # top-level scenes, this asset should be UNUSED.
            assert orphan_assets[0]["used"] is False
        finally:
            session._index.assets = original_assets
            session._index.resource_usage = original_usage
            session._index.scene_instances = original_instances


class TestAssetRegistryMissingFile:
    # AC: "Assets whose files are missing on disk are flagged with size=0"
    # Behavior: If an asset path exists in index but file is gone -> size=0
    # @category: edge-case
    # @dependency: GodotIQSession, ProjectIndex
    # @complexity: low
    # ROI: 60 | Business Value: 5 | Frequency: 2
    def test_registry_missing_file(self, session: GodotIQSession) -> None:
        """Missing asset files flagged (size=0)."""
        # SKELETON: inject a fake asset entry into the index with a non-existent
        # file path and verify the registry reports size=0
        original_assets = dict(session._index.assets)
        try:
            session._index.assets["res://assets/models/gone.glb"] = {
                "type": "model",
                "size_bytes": 0,
                "used_by": [],
            }
            result = _build_asset_registry(session, detail="full")
            # SKELETON: the missing file should appear in the registry
            assert "assets" in result
            gone_assets = [
                a for a in result["assets"]
                if "gone.glb" in a.get("path", "")
            ]
            assert len(gone_assets) >= 1
            # SKELETON: size should be 0 for missing files
            for a in gone_assets:
                size_key = "size" if "size" in a else "size_bytes"
                assert a[size_key] == 0
        finally:
            session._index.assets = original_assets
