"""Per-tool detail parameter tests for tools modified in Sprint 8a sections 02-06."""

from __future__ import annotations

import inspect
import json

import pytest

from godotiq.session import GodotIQSession
from godotiq.tools.assets import _build_suggest_scale
from godotiq.tools.code import _build_impact_check


BRIEF_LIMIT = 3_000   # 2000 + buffer for truncation metadata overhead
NORMAL_LIMIT = 10_000  # 8000 + buffer


def _output_size(result: dict) -> int:
    """Return JSON-serialized character count of a result dict."""
    return len(json.dumps(result, default=str, ensure_ascii=False))


class TestSuggestScaleDetail:
    """Tests for suggest_scale detail parameter support."""

    def test_suggest_scale_accepts_detail_parameter(self) -> None:
        """suggest_scale function signature includes detail parameter."""
        sig = inspect.signature(_build_suggest_scale)
        assert "detail" in sig.parameters

    def test_suggest_scale_default_returns_dict(self, session: GodotIQSession) -> None:
        """suggest_scale with no detail kwarg returns a dict result."""
        result = _build_suggest_scale(session, scene="res://main.tscn", intended_use="printer")
        assert isinstance(result, dict)

    def test_suggest_scale_brief_output_under_3000_chars(self, session: GodotIQSession) -> None:
        """suggest_scale(detail='brief') output < 3000 chars."""
        result = _build_suggest_scale(
            session, scene="res://main.tscn", intended_use="printer", detail="brief"
        )
        assert _output_size(result) < BRIEF_LIMIT

    def test_suggest_scale_normal_output_under_10000_chars(self, session: GodotIQSession) -> None:
        """suggest_scale(detail='normal') output < 10000 chars."""
        result = _build_suggest_scale(
            session, scene="res://main.tscn", intended_use="printer", detail="normal"
        )
        assert _output_size(result) < NORMAL_LIMIT


class TestImpactCheckRename:
    """Tests for impact_check parameter rename and detail support."""

    def test_accepts_change_description_parameter(self) -> None:
        """impact_check function signature has change_description parameter."""
        sig = inspect.signature(_build_impact_check)
        assert "change_description" in sig.parameters

    def test_returned_dict_has_change_description_field(self, session: GodotIQSession) -> None:
        """Result dict uses 'change_description' key, not 'detail' as data field."""
        result = _build_impact_check(
            session, file="res://scripts/autoloads/economy_manager.gd",
            action="modify_function", target="add_cash",
            change_description="rename add_cash to add_money",
        )
        assert "change_description" in result
        assert result["change_description"] == "rename add_cash to add_money"

    def test_safe_alternative_uses_change_description(self, session: GodotIQSession) -> None:
        """add_parameter action uses change_description in safe_alternative."""
        result = _build_impact_check(
            session, file="res://scripts/autoloads/economy_manager.gd",
            action="add_parameter", target="add_cash",
            change_description="tax_rate",
        )
        assert "tax_rate" in result.get("safe_alternative", "")

    def test_detail_brief_output_under_3000(self, session: GodotIQSession) -> None:
        """impact_check(detail='brief') output < 3000 chars."""
        result = _build_impact_check(
            session, file="res://scripts/autoloads/economy_manager.gd",
            action="modify_function", target="add_cash",
            detail="brief",
        )
        assert _output_size(result) < BRIEF_LIMIT

    def test_detail_normal_output_under_10000(self, session: GodotIQSession) -> None:
        """impact_check(detail='normal') output < 10000 chars."""
        result = _build_impact_check(
            session, file="res://scripts/autoloads/economy_manager.gd",
            action="modify_function", target="add_cash",
            detail="normal",
        )
        assert _output_size(result) < NORMAL_LIMIT

    def test_default_detail_is_normal(self) -> None:
        """Default detail value is 'normal'."""
        sig = inspect.signature(_build_impact_check)
        assert sig.parameters["detail"].default == "normal"


class TestPlacementDetail:
    """Tests for placement detail parameter support."""

    def test_placement_accepts_detail_parameter(self) -> None:
        """godotiq_placement function signature includes detail parameter."""
        from godotiq.tools.spatial import godotiq_placement
        sig = inspect.signature(godotiq_placement)
        assert "detail" in sig.parameters
        assert sig.parameters["detail"].default == "normal"


# --- Section 05: Bridge tools detail parameter tests ---

from unittest.mock import AsyncMock, patch

from godotiq.tools.assets import _build_asset_registry
from godotiq.tools.spatial import _build_scene_map, _build_spatial_audit
from godotiq.tools.code import _build_signal_map, _build_dependency_graph, _build_validate
from godotiq.tools.animation import _build_animation_info, _build_animation_audit
from godotiq.tools.memory import _build_project_summary, _build_file_context
from godotiq.tools.flow import _build_trace_flow


class TestBridgeToolSignatures:
    """All bridge tools that need detail added have correct signatures."""

    @pytest.mark.parametrize("tool_name", [
        "godotiq_screenshot",
        "godotiq_run",
        "godotiq_editor_context",
        "godotiq_node_ops",
        "godotiq_script_ops",
        "godotiq_file_ops",
        "godotiq_input",
        "godotiq_undo_history",
        "godotiq_save_scene",
        "godotiq_camera",
    ])
    def test_bridge_tool_accepts_detail(self, tool_name: str) -> None:
        """Each bridge tool accepts a detail parameter with default 'normal'."""
        import godotiq.tools.bridge as bridge_mod
        func = getattr(bridge_mod, tool_name)
        sig = inspect.signature(func)
        assert "detail" in sig.parameters, f"{tool_name} missing detail param"
        assert sig.parameters["detail"].default == "normal", f"{tool_name} default != 'normal'"

    def test_ping_accepts_detail(self) -> None:
        """godotiq_ping accepts detail parameter with default 'normal'."""
        from godotiq.server import godotiq_ping
        sig = inspect.signature(godotiq_ping)
        assert "detail" in sig.parameters
        assert sig.parameters["detail"].default == "normal"

    def test_watch_has_detail(self) -> None:
        """godotiq_watch already has detail — verify it still works."""
        from godotiq.tools.bridge import godotiq_watch
        sig = inspect.signature(godotiq_watch)
        assert "detail" in sig.parameters
        assert sig.parameters["detail"].default == "normal"


class TestBridgeHighOutputFiltering:
    """High-output bridge tools apply output limiting."""

    @pytest.mark.asyncio
    async def test_file_ops_brief_applies_limit(self) -> None:
        """file_ops with detail='brief' calls apply_output_limit."""
        from godotiq.tools.bridge import godotiq_file_ops
        large_result = {"entries": [{"name": f"file_{i}.gd"} for i in range(200)]}
        with patch("godotiq.tools.bridge._file_ops", new_callable=AsyncMock, return_value=large_result):
            result = await godotiq_file_ops(op="list", path="res://", detail="brief")
        size = _output_size(result)
        assert size < BRIEF_LIMIT

    @pytest.mark.asyncio
    async def test_file_ops_read_truncates_content(self) -> None:
        """file_ops op='read' with large content truncates string."""
        from godotiq.tools.bridge import godotiq_file_ops
        large_content = "x" * 20_000
        large_result = {"content": large_content, "path": "res://big.gd"}
        with patch("godotiq.tools.bridge._file_ops", new_callable=AsyncMock, return_value=large_result):
            result = await godotiq_file_ops(op="read", path="res://big.gd", detail="brief")
        assert len(result["content"]) < 2000
        assert result.get("content_truncated") is True

    @pytest.mark.asyncio
    async def test_node_ops_brief_applies_limit(self) -> None:
        """node_ops with detail='brief' calls apply_output_limit."""
        from godotiq.tools.bridge import godotiq_node_ops
        large_result = {"results": [{"node": f"Node{i}", "status": "ok"} for i in range(200)]}
        with patch("godotiq.tools.bridge._node_ops", new_callable=AsyncMock, return_value=large_result):
            result = await godotiq_node_ops(operations=[{"op": "move"}], detail="brief")
        size = _output_size(result)
        assert size < BRIEF_LIMIT

    @pytest.mark.asyncio
    async def test_editor_context_brief_applies_limit(self) -> None:
        """editor_context with detail='brief' calls apply_output_limit."""
        from godotiq.tools.bridge import godotiq_editor_context
        large_result = {"open_scenes": [f"scene_{i}.tscn" for i in range(200)]}
        with patch("godotiq.tools.bridge._editor_context", new_callable=AsyncMock, return_value=large_result):
            result = await godotiq_editor_context(detail="brief")
        size = _output_size(result)
        assert size < BRIEF_LIMIT

    @pytest.mark.asyncio
    async def test_input_brief_applies_limit(self) -> None:
        """input with detail='brief' calls apply_output_limit."""
        from godotiq.tools.bridge import godotiq_input
        large_result = {"side_effects": [{"key": f"val_{i}"} for i in range(200)]}
        with patch("godotiq.tools.bridge._input_sim", new_callable=AsyncMock, return_value=large_result):
            result = await godotiq_input(commands=[{"key": "space"}], detail="brief")
        size = _output_size(result)
        assert size < BRIEF_LIMIT

    @pytest.mark.asyncio
    async def test_watch_brief_applies_limit(self) -> None:
        """watch with detail='brief' applies apply_output_limit as safety net."""
        from godotiq.tools.bridge import godotiq_watch
        large_result = {"events": [{"ts": i, "val": f"v{i}"} for i in range(200)]}
        with patch("godotiq.tools.bridge._watch", new_callable=AsyncMock, return_value=large_result):
            result = await godotiq_watch(action="read", detail="brief")
        size = _output_size(result)
        assert size < BRIEF_LIMIT

    @pytest.mark.asyncio
    async def test_undo_history_brief_applies_limit(self) -> None:
        """undo_history with detail='brief' applies apply_output_limit."""
        from godotiq.tools.bridge import godotiq_undo_history
        large_result = {"godotiq_actions": [{"action": f"op_{i}"} for i in range(200)]}
        with patch("godotiq.tools.bridge._undo_history", new_callable=AsyncMock, return_value=large_result):
            result = await godotiq_undo_history(detail="brief")
        size = _output_size(result)
        assert size < BRIEF_LIMIT


class TestBridgeNoOpTools:
    """No-op bridge tools accept detail but don't filter."""

    @pytest.mark.asyncio
    async def test_screenshot_accepts_detail(self) -> None:
        """screenshot accepts detail but returns result unchanged."""
        from godotiq.tools.bridge import godotiq_screenshot
        mock_result = {"image": "base64data", "format": "webp"}
        with patch("godotiq.tools.bridge._screenshot", new_callable=AsyncMock, return_value=mock_result):
            result = await godotiq_screenshot(detail="brief")
        assert result == mock_result

    @pytest.mark.asyncio
    async def test_run_accepts_detail(self) -> None:
        """run accepts detail but returns result unchanged."""
        from godotiq.tools.bridge import godotiq_run
        mock_result = {"status": "playing"}
        with patch("godotiq.tools.bridge._run", new_callable=AsyncMock, return_value=mock_result):
            result = await godotiq_run(action="play", detail="brief")
        assert result == mock_result

    @pytest.mark.asyncio
    async def test_save_scene_accepts_detail(self) -> None:
        """save_scene accepts detail but returns result unchanged."""
        from godotiq.tools.bridge import godotiq_save_scene
        mock_result = {"saved": True}
        with patch("godotiq.tools.bridge._save_scene", new_callable=AsyncMock, return_value=mock_result):
            result = await godotiq_save_scene(detail="brief")
        assert result == mock_result

    @pytest.mark.asyncio
    async def test_camera_accepts_detail(self) -> None:
        """camera accepts detail but returns result unchanged."""
        from godotiq.tools.bridge import godotiq_camera
        mock_result = {"position": [0, 5, 10]}
        with patch("godotiq.tools.bridge._camera", new_callable=AsyncMock, return_value=mock_result):
            result = await godotiq_camera(action="get_position", detail="brief")
        assert result == mock_result

    @pytest.mark.asyncio
    async def test_script_ops_accepts_detail(self) -> None:
        """script_ops accepts detail but returns result unchanged."""
        from godotiq.tools.bridge import godotiq_script_ops
        mock_result = {"content": "extends Node", "path": "res://test.gd"}
        with patch("godotiq.tools.bridge._script_ops", new_callable=AsyncMock, return_value=mock_result):
            result = await godotiq_script_ops(op="read", path="res://test.gd", detail="brief")
        assert result == mock_result


# --- Section 06: Verify existing detail implementations ---


class TestAssetRegistryDetail:
    """Verify asset_registry respects character limits."""

    def test_brief_under_limit(self, session: GodotIQSession) -> None:
        result = _build_asset_registry(session, detail="brief")
        assert _output_size(result) < BRIEF_LIMIT

    def test_normal_under_limit(self, session: GodotIQSession) -> None:
        result = _build_asset_registry(session, detail="normal")
        assert _output_size(result) < NORMAL_LIMIT


class TestSceneMapDetail:
    """Verify scene_map respects character limits."""

    def test_brief_under_limit(self, session: GodotIQSession) -> None:
        result = _build_scene_map(session, scene_path="res://scenes/main.tscn", detail="brief")
        assert _output_size(result) < BRIEF_LIMIT

    def test_normal_under_limit(self, session: GodotIQSession) -> None:
        result = _build_scene_map(session, scene_path="res://scenes/main.tscn", detail="normal")
        assert _output_size(result) < NORMAL_LIMIT

    def test_full_preserves_30_node_cap(self) -> None:
        from godotiq.tools.spatial import _MAX_NEARBY
        assert _MAX_NEARBY["full"] == 30


class TestSpatialAuditDetail:
    """Verify spatial_audit respects character limits."""

    def test_brief_under_limit(self, session: GodotIQSession) -> None:
        result = _build_spatial_audit(session, scene="res://scenes/main.tscn", detail="brief")
        assert _output_size(result) < BRIEF_LIMIT

    def test_normal_under_limit(self, session: GodotIQSession) -> None:
        result = _build_spatial_audit(session, scene="res://scenes/main.tscn", detail="normal")
        assert _output_size(result) < NORMAL_LIMIT


class TestSignalMapDetail:
    """Verify signal_map respects character limits."""

    def test_brief_under_limit(self, session: GodotIQSession) -> None:
        result = _build_signal_map(session, detail="brief")
        assert _output_size(result) < BRIEF_LIMIT

    def test_normal_under_limit(self, session: GodotIQSession) -> None:
        result = _build_signal_map(session, detail="normal")
        assert _output_size(result) < NORMAL_LIMIT


class TestDependencyGraphDetail:
    """Verify dependency_graph respects character limits."""

    def test_brief_under_limit(self, session: GodotIQSession) -> None:
        result = _build_dependency_graph(
            session, file_path="res://scripts/autoloads/economy_manager.gd", detail="brief"
        )
        assert _output_size(result) < BRIEF_LIMIT

    def test_normal_under_limit(self, session: GodotIQSession) -> None:
        result = _build_dependency_graph(
            session, file_path="res://scripts/autoloads/economy_manager.gd", detail="normal"
        )
        assert _output_size(result) < NORMAL_LIMIT


class TestTraceFlowDetail:
    """Verify trace_flow respects character limits."""

    @pytest.mark.asyncio
    async def test_brief_under_limit(self, session: GodotIQSession) -> None:
        result = await _build_trace_flow(session, trigger="add_cash", detail="brief")
        assert _output_size(result) < BRIEF_LIMIT

    @pytest.mark.asyncio
    async def test_normal_under_limit(self, session: GodotIQSession) -> None:
        result = await _build_trace_flow(session, trigger="add_cash", detail="normal")
        assert _output_size(result) < NORMAL_LIMIT


class TestProjectSummaryDetail:
    """Verify project_summary respects character limits."""

    def test_brief_under_limit(self, session: GodotIQSession) -> None:
        result = _build_project_summary(session, detail="brief")
        assert _output_size(result) < BRIEF_LIMIT

    def test_normal_under_limit(self, session: GodotIQSession) -> None:
        result = _build_project_summary(session, detail="normal")
        assert _output_size(result) < NORMAL_LIMIT


class TestAnimationInfoDetail:
    """Verify animation_info respects character limits."""

    def test_brief_under_limit(self, anim_session: GodotIQSession) -> None:
        result = _build_animation_info(anim_session, scene_path="res://scenes/main.tscn", detail="brief")
        assert _output_size(result) < BRIEF_LIMIT

    def test_normal_under_limit(self, anim_session: GodotIQSession) -> None:
        result = _build_animation_info(anim_session, scene_path="res://scenes/main.tscn", detail="normal")
        assert _output_size(result) < NORMAL_LIMIT


class TestAnimationAuditDetail:
    """Verify animation_audit respects character limits."""

    def test_brief_under_limit(self, anim_session: GodotIQSession) -> None:
        result = _build_animation_audit(anim_session, detail="brief")
        assert _output_size(result) < BRIEF_LIMIT

    def test_normal_under_limit(self, anim_session: GodotIQSession) -> None:
        result = _build_animation_audit(anim_session, detail="normal")
        assert _output_size(result) < NORMAL_LIMIT


class TestValidateDetail:
    """Verify validate respects character limits."""

    def test_brief_under_limit(self, session: GodotIQSession) -> None:
        result = _build_validate(session, detail="brief")
        assert _output_size(result) < BRIEF_LIMIT

    def test_normal_under_limit(self, session: GodotIQSession) -> None:
        result = _build_validate(session, detail="normal")
        assert _output_size(result) < NORMAL_LIMIT


class TestFileContextDetail:
    """Verify file_context respects character limits."""

    def test_brief_under_limit(self, session: GodotIQSession) -> None:
        result = _build_file_context(
            session, file_path="res://scripts/autoloads/economy_manager.gd", detail="brief"
        )
        assert _output_size(result) < BRIEF_LIMIT

    def test_normal_under_limit(self, session: GodotIQSession) -> None:
        result = _build_file_context(
            session, file_path="res://scripts/autoloads/economy_manager.gd", detail="normal"
        )
        assert _output_size(result) < NORMAL_LIMIT
