"""Tests for bridge protocol dataclasses and message parsing."""

from __future__ import annotations

import json

import pytest

from godotiq.bridge.protocol import (
    BridgeRequest,
    BridgeResponse,
    BridgeEvent,
    parse_message,
    get_timeout,
    TOOL_TIMEOUTS,
)


class TestBridgeRequest:
    """BridgeRequest serialization."""

    def test_to_json_no_params(self) -> None:
        """to_json() with no params produces valid JSON with empty params dict."""
        req = BridgeRequest(id="1", method="ping", params={})
        raw = req.to_json()
        parsed = json.loads(raw)
        assert parsed["id"] == "1"
        assert parsed["method"] == "ping"
        assert parsed["params"] == {}

    def test_to_json_with_params(self) -> None:
        """to_json() with params preserves all param values."""
        req = BridgeRequest(id="2", method="screenshot", params={"scale": 0.5, "format": "webp"})
        raw = req.to_json()
        parsed = json.loads(raw)
        assert parsed["params"]["scale"] == 0.5
        assert parsed["params"]["format"] == "webp"


class TestParseMessage:
    """parse_message() routing and error handling."""

    def test_parse_ok_response(self) -> None:
        """Parse ok response extracts id, status, result correctly."""
        text = json.dumps({"id": "1", "status": "ok", "result": {"version": "4.3"}})
        msg = parse_message(text)
        assert isinstance(msg, BridgeResponse)
        assert msg.id == "1"
        assert msg.status == "ok"
        assert msg.result == {"version": "4.3"}
        assert msg.is_ok

    def test_parse_error_response(self) -> None:
        """Parse error response extracts error code and message."""
        text = json.dumps({
            "id": "2",
            "status": "error",
            "error": {"code": "GAME_NOT_RUNNING", "message": "Start the game first"},
        })
        msg = parse_message(text)
        assert isinstance(msg, BridgeResponse)
        assert not msg.is_ok
        assert msg.error_code == "GAME_NOT_RUNNING"
        assert msg.error_message == "Start the game first"

    def test_parse_event(self) -> None:
        """Parse event message returns BridgeEvent with event name and data."""
        text = json.dumps({"event": "game_started", "data": {}})
        msg = parse_message(text)
        assert isinstance(msg, BridgeEvent)
        assert msg.event == "game_started"

    def test_parse_event_with_data(self) -> None:
        """Parse event with data preserves nested data fields."""
        text = json.dumps({"event": "runtime_error", "data": {"message": "null ref", "line": 42}})
        msg = parse_message(text)
        assert isinstance(msg, BridgeEvent)
        assert msg.data["message"] == "null ref"
        assert msg.data["line"] == 42

    def test_invalid_json_raises(self) -> None:
        """Invalid JSON raises ValueError with 'Invalid JSON' message."""
        with pytest.raises(ValueError, match="Invalid JSON"):
            parse_message("not json {{{")

    def test_missing_id_and_event_raises(self) -> None:
        """Message without id or event raises ValueError."""
        with pytest.raises(ValueError):
            parse_message(json.dumps({"status": "ok"}))

    def test_non_object_json_raises(self) -> None:
        """Non-object JSON (string, array) raises ValueError."""
        with pytest.raises(ValueError):
            parse_message(json.dumps("just a string"))
        with pytest.raises(ValueError):
            parse_message(json.dumps([1, 2, 3]))


class TestTimeouts:
    """get_timeout() and TOOL_TIMEOUTS configuration."""

    def test_known_method_screenshot(self) -> None:
        """Known method returns configured timeout (screenshot=30)."""
        assert get_timeout("screenshot") == 30.0

    def test_known_method_ping(self) -> None:
        """Known method returns configured timeout (ping=2)."""
        assert get_timeout("ping") == 2.0

    def test_unknown_method_default(self) -> None:
        """Unknown method returns default 5.0."""
        assert get_timeout("nonexistent_method") == 5.0

    def test_scene_tree_timeout(self) -> None:
        """scene_tree has a 5-second timeout configured."""
        assert get_timeout("scene_tree") == 5.0

    def test_node_ops_timeout(self) -> None:
        """node_ops has a 10-second timeout configured."""
        assert get_timeout("node_ops") == 10.0

    def test_all_timeouts_positive(self) -> None:
        """All configured timeouts are positive."""
        for method, timeout in TOOL_TIMEOUTS.items():
            assert timeout > 0, f"Timeout for {method} is not positive: {timeout}"
