"""Tests for infrastructure.protocol — WebSocket message types."""

import json

import pytest

from infrastructure.protocol import (
    AgentStateMessage,
    BarkMessage,
    ConnectionAckMessage,
    ErrorMessage,
    MemoryFlashMessage,
    MessageType,
    SystemStatusMessage,
    TaskUpdateMessage,
    ThoughtMessage,
    VisitorStateMessage,
    WSMessage,
)

# ---------------------------------------------------------------------------
# MessageType enum
# ---------------------------------------------------------------------------


class TestMessageType:
    """MessageType enum covers all 9 Matrix PROTOCOL.md types."""

    def test_has_all_nine_types(self):
        assert len(MessageType) == 9

    @pytest.mark.parametrize(
        "member,value",
        [
            (MessageType.AGENT_STATE, "agent_state"),
            (MessageType.VISITOR_STATE, "visitor_state"),
            (MessageType.BARK, "bark"),
            (MessageType.THOUGHT, "thought"),
            (MessageType.SYSTEM_STATUS, "system_status"),
            (MessageType.CONNECTION_ACK, "connection_ack"),
            (MessageType.ERROR, "error"),
            (MessageType.TASK_UPDATE, "task_update"),
            (MessageType.MEMORY_FLASH, "memory_flash"),
        ],
    )
    def test_enum_values(self, member, value):
        assert member.value == value

    def test_str_comparison(self):
        """MessageType is a str enum so it can be compared to plain strings."""
        assert MessageType.BARK == "bark"


# ---------------------------------------------------------------------------
# to_json / from_json round-trip
# ---------------------------------------------------------------------------


class TestAgentStateMessage:
    def test_defaults(self):
        msg = AgentStateMessage()
        assert msg.type == "agent_state"
        assert msg.agent_id == ""
        assert msg.data == {}

    def test_round_trip(self):
        msg = AgentStateMessage(agent_id="timmy", data={"mood": "happy"}, ts=1000.0)
        raw = msg.to_json()
        restored = AgentStateMessage.from_json(raw)
        assert restored.agent_id == "timmy"
        assert restored.data == {"mood": "happy"}
        assert restored.ts == 1000.0

    def test_to_json_structure(self):
        msg = AgentStateMessage(agent_id="timmy", data={"x": 1}, ts=123.0)
        parsed = json.loads(msg.to_json())
        assert parsed["type"] == "agent_state"
        assert parsed["agent_id"] == "timmy"
        assert parsed["data"] == {"x": 1}
        assert parsed["ts"] == 123.0


class TestVisitorStateMessage:
    def test_round_trip(self):
        msg = VisitorStateMessage(visitor_id="v1", data={"page": "/"}, ts=1.0)
        restored = VisitorStateMessage.from_json(msg.to_json())
        assert restored.visitor_id == "v1"
        assert restored.data == {"page": "/"}


class TestBarkMessage:
    def test_round_trip(self):
        msg = BarkMessage(agent_id="timmy", content="woof!", ts=1.0)
        restored = BarkMessage.from_json(msg.to_json())
        assert restored.agent_id == "timmy"
        assert restored.content == "woof!"


class TestThoughtMessage:
    def test_round_trip(self):
        msg = ThoughtMessage(agent_id="timmy", content="hmm...", ts=1.0)
        restored = ThoughtMessage.from_json(msg.to_json())
        assert restored.content == "hmm..."


class TestSystemStatusMessage:
    def test_round_trip(self):
        msg = SystemStatusMessage(status="healthy", data={"uptime": 3600}, ts=1.0)
        restored = SystemStatusMessage.from_json(msg.to_json())
        assert restored.status == "healthy"
        assert restored.data == {"uptime": 3600}


class TestConnectionAckMessage:
    def test_round_trip(self):
        msg = ConnectionAckMessage(client_id="abc-123", ts=1.0)
        restored = ConnectionAckMessage.from_json(msg.to_json())
        assert restored.client_id == "abc-123"


class TestErrorMessage:
    def test_round_trip(self):
        msg = ErrorMessage(code="INVALID", message="bad request", ts=1.0)
        restored = ErrorMessage.from_json(msg.to_json())
        assert restored.code == "INVALID"
        assert restored.message == "bad request"


class TestTaskUpdateMessage:
    def test_round_trip(self):
        msg = TaskUpdateMessage(task_id="t1", status="completed", data={"result": "ok"}, ts=1.0)
        restored = TaskUpdateMessage.from_json(msg.to_json())
        assert restored.task_id == "t1"
        assert restored.status == "completed"
        assert restored.data == {"result": "ok"}


class TestMemoryFlashMessage:
    def test_round_trip(self):
        msg = MemoryFlashMessage(agent_id="timmy", memory_key="fav_food", content="kibble", ts=1.0)
        restored = MemoryFlashMessage.from_json(msg.to_json())
        assert restored.memory_key == "fav_food"
        assert restored.content == "kibble"


# ---------------------------------------------------------------------------
# WSMessage.from_json dispatch
# ---------------------------------------------------------------------------


class TestWSMessageDispatch:
    """WSMessage.from_json dispatches to the correct subclass."""

    def test_dispatch_to_bark(self):
        raw = json.dumps({"type": "bark", "agent_id": "t", "content": "woof", "ts": 1.0})
        msg = WSMessage.from_json(raw)
        assert isinstance(msg, BarkMessage)
        assert msg.content == "woof"

    def test_dispatch_to_error(self):
        raw = json.dumps({"type": "error", "code": "E1", "message": "oops", "ts": 1.0})
        msg = WSMessage.from_json(raw)
        assert isinstance(msg, ErrorMessage)

    def test_unknown_type_returns_base(self):
        raw = json.dumps({"type": "unknown_future_type", "ts": 1.0})
        msg = WSMessage.from_json(raw)
        assert type(msg) is WSMessage
        assert msg.type == "unknown_future_type"

    def test_invalid_json_raises(self):
        with pytest.raises(json.JSONDecodeError):
            WSMessage.from_json("not json")
