"""Tests for the project index module."""

from __future__ import annotations

import os
from pathlib import Path

import pytest

from godotiq.cache.file_cache import FileCache
from godotiq.parsers.project_index import (
    ProjectIndex,
    _parse_project_godot,
    _to_res_path,
    find_asset_usage,
    find_unused_assets,
    get_scene_dependency_chain,
    scan_project,
    rescan_changed,
)


@pytest.fixture(scope="module")
def _sample_root() -> Path:
    return Path(__file__).resolve().parent.parent / "fixtures" / "sample_project"


@pytest.fixture(scope="module")
def scanned_index(_sample_root: Path) -> ProjectIndex:
    return scan_project(_sample_root)


# ---------------------------------------------------------------------------
# Unit Tests -- project.godot Parsing
# ---------------------------------------------------------------------------


class TestParseProjectGodot:
    def test_parse_project_name(self, _sample_root: Path) -> None:
        name, _ = _parse_project_godot(_sample_root / "project.godot")
        assert name == "Sample Project"

    def test_parse_autoloads(self, _sample_root: Path) -> None:
        _, autoloads = _parse_project_godot(_sample_root / "project.godot")
        assert len(autoloads) == 3
        assert "GameManager" in autoloads
        assert autoloads["GameManager"] == "res://scripts/entities/printer.gd"
        assert "EconomyManager" in autoloads
        assert autoloads["EconomyManager"] == "res://scripts/autoloads/economy_manager.gd"
        assert "ProgressionManager" in autoloads
        assert autoloads["ProgressionManager"] == "res://scripts/autoloads/progression_manager.gd"

    def test_parse_empty_autoloads(self, tmp_path: Path) -> None:
        godot_file = tmp_path / "project.godot"
        godot_file.write_text('[application]\n\nconfig/name="Empty"\n')
        _, autoloads = _parse_project_godot(godot_file)
        assert autoloads == {}

    def test_parse_project_godot_missing(self, tmp_path: Path) -> None:
        with pytest.raises(FileNotFoundError):
            _parse_project_godot(tmp_path / "nonexistent.godot")


# ---------------------------------------------------------------------------
# Unit Tests -- Path Conversion
# ---------------------------------------------------------------------------


class TestToResPath:
    def test_to_res_path(self) -> None:
        result = _to_res_path(Path("/some/project/scenes/main.tscn"), Path("/some/project"))
        assert result == "res://scenes/main.tscn"

    def test_to_res_path_nested(self) -> None:
        result = _to_res_path(
            Path("/project/assets/models/printers/printer_standard.glb"),
            Path("/project"),
        )
        assert result == "res://assets/models/printers/printer_standard.glb"


# ---------------------------------------------------------------------------
# Integration Tests -- Project Scanning
# ---------------------------------------------------------------------------


class TestScanProject:
    def test_scan_finds_all_tscn(self, scanned_index: ProjectIndex) -> None:
        assert scanned_index.total_scenes == 2
        assert "res://scenes/main.tscn" in scanned_index.scenes
        assert "res://scenes/equipment/fdm_printer.tscn" in scanned_index.scenes

    def test_scan_finds_all_scripts(self, scanned_index: ProjectIndex) -> None:
        assert scanned_index.total_scripts == 4
        assert "res://scripts/entities/printer.gd" in scanned_index.scripts
        assert "res://scripts/entities/worker.gd" in scanned_index.scripts
        assert "res://scripts/autoloads/economy_manager.gd" in scanned_index.scripts
        assert "res://scripts/autoloads/progression_manager.gd" in scanned_index.scripts

    def test_scan_finds_assets(self, scanned_index: ProjectIndex) -> None:
        assert scanned_index.total_assets >= 2
        glb_path = "res://assets/models/printers/printer_standard.glb"
        assert glb_path in scanned_index.assets
        assert scanned_index.assets[glb_path]["type"] == "model"
        assert isinstance(scanned_index.assets[glb_path]["size_bytes"], int)

    def test_scan_skips_godot_dir(self, tmp_path: Path) -> None:
        (tmp_path / "project.godot").write_text('[application]\nconfig/name="Test"\n')
        godot_dir = tmp_path / ".godot"
        godot_dir.mkdir()
        (godot_dir / "hidden.tscn").write_text('[gd_scene format=3]\n[node name="X" type="Node3D"]\n')
        index = scan_project(tmp_path)
        assert "res://.godot/hidden.tscn" not in index.scenes

    def test_scan_skips_addons(self, tmp_path: Path) -> None:
        (tmp_path / "project.godot").write_text('[application]\nconfig/name="Test"\n')
        addons_dir = tmp_path / "addons"
        addons_dir.mkdir()
        (addons_dir / "plugin.tscn").write_text('[gd_scene format=3]\n[node name="X" type="Node3D"]\n')
        index = scan_project(tmp_path)
        assert "res://addons/plugin.tscn" not in index.scenes

    def test_scan_project_not_found(self, tmp_path: Path) -> None:
        with pytest.raises(FileNotFoundError):
            scan_project(tmp_path / "nonexistent")


# ---------------------------------------------------------------------------
# Integration Tests -- Cross-Reference Maps
# ---------------------------------------------------------------------------


class TestCrossReferences:
    def test_scene_instances_map(self, scanned_index: ProjectIndex) -> None:
        instances = scanned_index.scene_instances.get("res://scenes/main.tscn", [])
        assert "res://scenes/equipment/fdm_printer.tscn" in instances

    def test_scene_instance_of_map(self, scanned_index: ProjectIndex) -> None:
        instance_of = scanned_index.scene_instance_of.get(
            "res://scenes/equipment/fdm_printer.tscn", []
        )
        assert "res://scenes/main.tscn" in instance_of

    def test_script_to_scenes_map(self, scanned_index: ProjectIndex) -> None:
        scenes = scanned_index.script_to_scenes.get(
            "res://scripts/entities/printer.gd", []
        )
        assert "res://scenes/equipment/fdm_printer.tscn" in scenes

    def test_resource_usage(self, scanned_index: ProjectIndex) -> None:
        usage = scanned_index.resource_usage.get(
            "res://assets/models/printers/printer_standard.glb", []
        )
        assert "res://scenes/equipment/fdm_printer.tscn" in usage


# ---------------------------------------------------------------------------
# Integration Tests -- Query Functions
# ---------------------------------------------------------------------------


class TestQueryFunctions:
    def test_find_asset_usage(self, scanned_index: ProjectIndex) -> None:
        result = find_asset_usage(
            scanned_index, "res://assets/models/printers/printer_standard.glb"
        )
        assert len(result) > 0

    def test_find_unused_assets(self, scanned_index: ProjectIndex) -> None:
        unused = find_unused_assets(scanned_index)
        assert "res://assets/models/printers/unused_model.glb" in unused
        assert "res://assets/models/printers/printer_standard.glb" not in unused

    def test_scene_dependency_chain(self, scanned_index: ProjectIndex) -> None:
        chain = get_scene_dependency_chain(scanned_index, "res://scenes/main.tscn")
        assert "res://scenes/equipment/fdm_printer.tscn" in chain


# ---------------------------------------------------------------------------
# Integration Tests -- FileCache
# ---------------------------------------------------------------------------


class TestFileCache:
    def test_scan_with_cache(self, _sample_root: Path) -> None:
        cache = FileCache()
        index1 = scan_project(_sample_root, cache)
        index2 = scan_project(_sample_root, cache)
        assert index1.total_scenes == index2.total_scenes
        assert index1.total_scripts == index2.total_scripts

    def test_incremental_rescan(self, _sample_root: Path) -> None:
        cache = FileCache()
        index1 = scan_project(_sample_root, cache)

        # Touch a file to make it stale
        main_tscn = _sample_root / "scenes" / "main.tscn"
        os.utime(main_tscn, None)

        index2 = rescan_changed(index1, cache)
        assert index2.total_scenes == index1.total_scenes
        assert "res://scenes/main.tscn" in index2.scenes
