"""Tests for godotiq_validate tool."""

from __future__ import annotations

from godotiq.parsers.gd_parser import GdScript, GdVariable
from godotiq.session import GodotIQSession
from godotiq.tools.code import _build_validate


class TestValidateSingleFile:
    def test_validate_single_file(self, session: GodotIQSession) -> None:
        """Single-file validation returns issues for that file only."""
        result = _build_validate(session, "res://scripts/entities/worker.gd")
        assert result["target"] == "res://scripts/entities/worker.gd"
        assert result["total_files_checked"] == 1
        for issue in result["issues"]:
            assert issue["file"] == "res://scripts/entities/worker.gd"


class TestValidateProject:
    def test_validate_project(self, session: GodotIQSession) -> None:
        """Project-wide validation checks all scripts."""
        result = _build_validate(session)
        assert result["target"] == "project"
        assert result["total_files_checked"] == 4
        assert result["total_issues"] == len(result["issues"])
        assert "by_severity" in result


class TestValidateClassNameMissing:
    def test_validate_class_name_missing(self, session: GodotIQSession) -> None:
        """worker.gd has no class_name and is NOT autoload -> class_name_missing."""
        result = _build_validate(session, "res://scripts/entities/worker.gd")
        class_name_issues = [
            i for i in result["issues"] if i["rule"] == "class_name_missing"
        ]
        assert len(class_name_issues) == 1
        assert class_name_issues[0]["severity"] == "warning"

    def test_validate_class_name_not_triggered_for_autoload(
        self, session: GodotIQSession
    ) -> None:
        """printer.gd has no class_name but IS autoload -> no class_name_missing."""
        result = _build_validate(session, "res://scripts/entities/printer.gd")
        class_name_issues = [
            i for i in result["issues"] if i["rule"] == "class_name_missing"
        ]
        assert len(class_name_issues) == 0


class TestValidateNoTypeHint:
    def test_validate_no_type_hint(self, session: GodotIQSession) -> None:
        """Inject a fake script with untyped class vars to test detection."""
        fake_gd = GdScript()
        fake_gd.variables = [
            GdVariable(name="count", type_hint="", scope="class", line=5),
            GdVariable(name="typed_var", type_hint="int", scope="class", line=6),
            GdVariable(name="local_var", type_hint="", scope="local", line=7),
        ]
        session._gd_scripts["res://scripts/fake_untyped.gd"] = fake_gd
        try:
            result = _build_validate(session, "res://scripts/fake_untyped.gd")
            type_issues = [
                i for i in result["issues"] if i["rule"] == "no_type_hint"
            ]
            assert len(type_issues) == 1
            assert type_issues[0]["line"] == 5
            assert "count" in type_issues[0]["detail"]
        finally:
            del session._gd_scripts["res://scripts/fake_untyped.gd"]


class TestValidateOrphanSignal:
    def test_validate_orphan_signal(self, session: GodotIQSession) -> None:
        """cash_changed is emitted but has 0 listeners -> orphan_signal."""
        result = _build_validate(
            session, "res://scripts/autoloads/economy_manager.gd"
        )
        orphan_issues = [
            i for i in result["issues"] if i["rule"] == "orphan_signal"
        ]
        orphan_names = [i["detail"] for i in orphan_issues]
        assert any("cash_changed" in d for d in orphan_names)


class TestValidateSeverityOrder:
    def test_validate_severity_order(self, session: GodotIQSession) -> None:
        """Issues are sorted by severity: error first, then warning, then info."""
        result = _build_validate(session)
        severity_order = {"error": 0, "warning": 1, "info": 2}
        issues = result["issues"]
        for i in range(len(issues) - 1):
            current = severity_order[issues[i]["severity"]]
            next_val = severity_order[issues[i + 1]["severity"]]
            assert current <= next_val


class TestValidateTruncation:
    def test_validate_truncation(self, session: GodotIQSession) -> None:
        """Inject 110+ issues worth of data, verify cap at 100."""
        original_scripts = dict(session._gd_scripts)
        try:
            for i in range(60):
                fake_gd = GdScript()
                fake_gd.variables = [
                    GdVariable(name=f"v{j}", type_hint="", scope="class", line=j)
                    for j in range(3)
                ]
                session._gd_scripts[f"res://scripts/fake_{i}.gd"] = fake_gd
            result = _build_validate(session)
            assert len(result["issues"]) <= 100
            assert result.get("truncated") is True
        finally:
            session._gd_scripts = original_scripts


class TestValidateWithConfig:
    def test_validate_with_config(self, session: GodotIQSession) -> None:
        """Disabled rules are not reported."""
        original_conventions = dict(session.config.conventions)
        try:
            session.config.conventions = {
                "disabled_rules": ["class_name_missing"]
            }
            result = _build_validate(session, "res://scripts/entities/worker.gd")
            class_name_issues = [
                i for i in result["issues"] if i["rule"] == "class_name_missing"
            ]
            assert len(class_name_issues) == 0
        finally:
            session.config.conventions = original_conventions
