"""Tests for godotiq_signal_map tool."""

from __future__ import annotations

import pytest

from godotiq.session import GodotIQSession
from godotiq.tools.code import _build_signal_map


class TestSignalMapAll:
    def test_signal_map_all(self, session: GodotIQSession) -> None:
        """Returns total_signals, total_emissions, total_connections as integers."""
        result = _build_signal_map(session)
        assert isinstance(result["total_signals_defined"], int)
        assert isinstance(result["total_emissions"], int)
        assert isinstance(result["total_connections"], int)
        # Fixture has 6 signals defined (3 in economy, 3 in progression)
        assert result["total_signals_defined"] == 6
        # Emissions: cash_changed x2, insufficient_funds x1, xp_gained x1, level_up x1
        assert result["total_emissions"] == 5
        assert "signals" in result

    def test_signal_map_orphans(self, session: GodotIQSession) -> None:
        """Orphans list has signals with 0 listeners."""
        result = _build_signal_map(session, find="orphans")
        assert "orphans" in result
        # No signal_connections in fixture → all emitted signals are orphans
        orphan_names = [o["name"] for o in result["orphans"]]
        assert "cash_changed" in orphan_names
        for orphan in result["orphans"]:
            assert orphan["listeners"] == 0

    def test_signal_map_busiest(self, session: GodotIQSession) -> None:
        """Busiest sorted by listener_count descending."""
        result = _build_signal_map(session, find="busiest")
        assert "busiest" in result
        listeners = [b["listeners"] for b in result["busiest"]]
        assert listeners == sorted(listeners, reverse=True)

    def test_signal_map_missing(self, session: GodotIQSession) -> None:
        """Detects emitted signals not defined anywhere in the project."""
        result = _build_signal_map(session, find="missing")
        assert "missing" in result
        # In the fixture, all emissions match definitions in the same file
        assert len(result["missing"]) == 0

        # Patch: add a script that emits a signal it doesn't define
        from godotiq.parsers.gd_parser import GdScript, GdSignalEmission
        fake_gd = GdScript()
        fake_gd.signal_emissions = [GdSignalEmission(signal_name="phantom_signal", arg_count=0, line=10)]
        session._gd_scripts["res://scripts/fake.gd"] = fake_gd
        try:
            result2 = _build_signal_map(session, find="missing")
            missing_names = [m["name"] for m in result2["missing"]]
            assert "phantom_signal" in missing_names
        finally:
            del session._gd_scripts["res://scripts/fake.gd"]


class TestSignalMapScope:
    def test_signal_map_scope_file(self, session: GodotIQSession) -> None:
        """Filters to single file's signals only."""
        result = _build_signal_map(
            session, scope="file:res://scripts/autoloads/economy_manager.gd"
        )
        assert result["total_signals_defined"] == 3
        # Only economy_manager signals
        if "signals" in result:
            for sig in result["signals"]:
                for d in sig["defined_in"]:
                    assert "economy_manager" in d

    def test_signal_map_scope_autoloads(self, session: GodotIQSession) -> None:
        """Filters to autoload scripts only."""
        result = _build_signal_map(session, scope="autoloads")
        # economy_manager + progression_manager are autoloads (both have signals)
        assert result["total_signals_defined"] == 6
        if "signals" in result:
            for sig in result["signals"]:
                for d in sig["defined_in"]:
                    assert "autoloads" in d or "economy" in d or "progression" in d


class TestSignalMapEventBus:
    def test_signal_map_events_bus_not_missing(self, session: GodotIQSession) -> None:
        """Signal emitted via Events bus but defined in events.gd should NOT be flagged as missing."""
        from godotiq.parsers.gd_parser import GdScript, GdSignal, GdSignalEmission

        events_gd = GdScript()
        events_gd.signals = [
            GdSignal(name="order_accepted", args=[], line=1),
            GdSignal(name="day_started", args=[], line=2),
        ]
        emitter_gd = GdScript()
        emitter_gd.signal_emissions = [
            GdSignalEmission(signal_name="order_accepted", arg_count=0, line=5),
        ]
        session._gd_scripts["res://scripts/autoloads/events_bus.gd"] = events_gd
        session._gd_scripts["res://scripts/bus_emitter.gd"] = emitter_gd
        try:
            result = _build_signal_map(session, find="missing")
            assert "missing" in result
            missing_names = [m["name"] for m in result["missing"]]
            assert "order_accepted" not in missing_names
        finally:
            del session._gd_scripts["res://scripts/autoloads/events_bus.gd"]
            del session._gd_scripts["res://scripts/bus_emitter.gd"]

    def test_signal_map_truly_missing(self, session: GodotIQSession) -> None:
        """Signal emitted but not defined anywhere in the project is flagged as missing."""
        from godotiq.parsers.gd_parser import GdScript, GdSignalEmission

        broken_gd = GdScript()
        broken_gd.signal_emissions = [
            GdSignalEmission(signal_name="totally_fake_signal", arg_count=0, line=10),
        ]
        session._gd_scripts["res://scripts/broken_emitter.gd"] = broken_gd
        try:
            result = _build_signal_map(session, find="missing")
            missing_names = [m["name"] for m in result["missing"]]
            assert "totally_fake_signal" in missing_names
            matching = [m for m in result["missing"] if m["name"] == "totally_fake_signal"]
            assert len(matching) == 1
            assert matching[0]["emitted_in"] == "res://scripts/broken_emitter.gd"
        finally:
            del session._gd_scripts["res://scripts/broken_emitter.gd"]


class TestSignalMapTruncation:
    def test_signal_map_truncation(self, session: GodotIQSession) -> None:
        """Signals list capped at 30 entries."""
        # Inject many fake signals to test truncation
        from godotiq.parsers.gd_parser import GdScript, GdSignal
        fake_gd = GdScript()
        fake_gd.signals = [GdSignal(name=f"sig_{i}", args=[], line=i) for i in range(40)]
        session._gd_scripts["res://scripts/many_signals.gd"] = fake_gd
        try:
            result = _build_signal_map(session)
            assert len(result["signals"]) <= 30
            assert result.get("truncated") is True
        finally:
            del session._gd_scripts["res://scripts/many_signals.gd"]
