"""
Tests for the Signaling Server.

Tests the WebRTC signaling server functionality:
- SSE client registration
- Message relay
- Message buffering for offline peers
- Status endpoint
"""

from io import BytesIO
from unittest.mock import MagicMock

import pytest

from mcard.ptr.core.signaling_server import (
    SignalingHandler,
    SignalingServer,
    run_server,
)


@pytest.fixture
def signaling():
    """Create a fresh SignalingServer instance."""
    return SignalingServer()


class TestSignalingServer:
    """Tests for SignalingServer class."""

    def test_initial_state(self, signaling):
        """New server should have no clients or buffered messages."""
        status = signaling.get_status()

        assert status["connected_peers"] == []
        assert status["buffered_messages"] == {}

    def test_register_client(self, signaling):
        """Should register a client."""
        mock_response = MagicMock()
        mock_response.wfile = BytesIO()

        signaling.register_client("peer-1", mock_response)

        status = signaling.get_status()
        assert "peer-1" in status["connected_peers"]

    def test_unregister_client(self, signaling):
        """Should unregister a client."""
        mock_response = MagicMock()
        mock_response.wfile = BytesIO()

        signaling.register_client("peer-1", mock_response)
        signaling.unregister_client("peer-1")

        status = signaling.get_status()
        assert "peer-1" not in status["connected_peers"]

    def test_unregister_nonexistent_client(self, signaling):
        """Should handle unregistering non-existent client."""
        # Should not raise
        signaling.unregister_client("nonexistent")

        status = signaling.get_status()
        assert status["connected_peers"] == []

    def test_relay_to_online_peer(self, signaling):
        """Should relay message to online peer."""
        mock_response = MagicMock()
        mock_response.wfile = BytesIO()

        signaling.register_client("peer-1", mock_response)

        message = {"type": "offer", "sdp": "test-sdp"}
        result = signaling.relay_message("peer-1", message)

        assert result is True
        # Verify SSE format
        sent_data = mock_response.wfile.getvalue().decode("utf-8")
        assert "data:" in sent_data
        assert "offer" in sent_data

    def test_relay_to_offline_peer_buffers(self, signaling):
        """Should buffer message for offline peer."""
        message = {"type": "offer", "sdp": "test-sdp"}
        result = signaling.relay_message("offline-peer", message)

        assert result is False  # Not delivered

        status = signaling.get_status()
        assert "offline-peer" in status["buffered_messages"]
        assert status["buffered_messages"]["offline-peer"] == 1

    def test_flush_buffered_on_connect(self, signaling):
        """Should flush buffered messages when peer connects."""
        # Buffer some messages
        signaling.relay_message("peer-1", {"type": "offer", "sdp": "offer-1"})
        signaling.relay_message("peer-1", {"type": "candidate", "candidate": "ice-1"})

        # Verify buffered
        status = signaling.get_status()
        assert status["buffered_messages"]["peer-1"] == 2

        # Connect
        mock_response = MagicMock()
        mock_response.wfile = BytesIO()

        signaling.register_client("peer-1", mock_response)

        # Buffer should be flushed
        status = signaling.get_status()
        assert "peer-1" not in status["buffered_messages"]

        # Messages should be sent
        sent_data = mock_response.wfile.getvalue().decode("utf-8")
        assert "offer" in sent_data
        assert "candidate" in sent_data

    def test_multiple_message_types(self, signaling):
        """Should handle different message types."""
        mock_response = MagicMock()
        mock_response.wfile = BytesIO()

        signaling.register_client("peer-1", mock_response)

        messages = [
            {"type": "offer", "sdp": "..."},
            {"type": "answer", "sdp": "..."},
            {"type": "candidate", "candidate": {"sdpMid": "0"}},
        ]

        for msg in messages:
            signaling.relay_message("peer-1", msg)

        sent_data = mock_response.wfile.getvalue().decode("utf-8")
        assert "offer" in sent_data
        assert "answer" in sent_data
        assert "candidate" in sent_data


class TestSignalingServerMultiplePeers:
    """Tests for multi-peer scenarios."""

    def test_multiple_peers(self, signaling):
        """Should handle multiple peers independently."""
        mock_resp_1 = MagicMock()
        mock_resp_1.wfile = BytesIO()

        mock_resp_2 = MagicMock()
        mock_resp_2.wfile = BytesIO()

        signaling.register_client("peer-1", mock_resp_1)
        signaling.register_client("peer-2", mock_resp_2)

        status = signaling.get_status()
        assert set(status["connected_peers"]) == {"peer-1", "peer-2"}

    def test_relay_to_correct_peer(self, signaling):
        """Should relay message only to target peer."""
        mock_resp_1 = MagicMock()
        mock_resp_1.wfile = BytesIO()

        mock_resp_2 = MagicMock()
        mock_resp_2.wfile = BytesIO()

        signaling.register_client("peer-1", mock_resp_1)
        signaling.register_client("peer-2", mock_resp_2)

        # Send to peer-1 only
        signaling.relay_message("peer-1", {"type": "offer"})

        # peer-1 should receive it
        assert b"offer" in mock_resp_1.wfile.getvalue()

        # peer-2 should NOT receive it
        assert b"offer" not in mock_resp_2.wfile.getvalue()

    def test_peer_disconnect_cleanup(self, signaling):
        """Should clean up when peer disconnects."""
        mock_resp = MagicMock()
        mock_resp.wfile = BytesIO()

        signaling.register_client("peer-1", mock_resp)
        signaling.register_client("peer-2", MagicMock())

        signaling.unregister_client("peer-1")

        status = signaling.get_status()
        assert status["connected_peers"] == ["peer-2"]


class TestSignalingHandlerUnit:
    """Unit tests for SignalingHandler (without running server)."""

    def test_cors_headers(self):
        """Should set CORS headers."""
        handler = SignalingHandler.__new__(SignalingHandler)
        handler.headers = {}

        # Mock methods
        sent_headers = {}

        def mock_send_header(name, value):
            sent_headers[name] = value

        handler.send_header = mock_send_header
        handler._set_cors_headers()

        assert sent_headers["Access-Control-Allow-Origin"] == "*"
        assert sent_headers["Access-Control-Allow-Methods"] == "GET, POST, OPTIONS"


class TestSignalingServerIntegration:
    """Integration tests for complete workflows."""

    def test_offer_answer_exchange(self, signaling):
        """Simulate a WebRTC offer/answer exchange."""
        # Setup peers
        alice_resp = MagicMock()
        alice_resp.wfile = BytesIO()

        bob_resp = MagicMock()
        bob_resp.wfile = BytesIO()

        signaling.register_client("alice", alice_resp)
        signaling.register_client("bob", bob_resp)

        # Alice sends offer to Bob
        offer = {
            "type": "offer",
            "source": "alice",
            "target": "bob",
            "sdp": "v=0\r\no=- 123456 ...",
        }
        signaling.relay_message("bob", offer)

        # Bob receives offer
        bob_data = bob_resp.wfile.getvalue().decode("utf-8")
        assert "offer" in bob_data
        assert "alice" in bob_data

        # Bob sends answer to Alice
        answer = {
            "type": "answer",
            "source": "bob",
            "target": "alice",
            "sdp": "v=0\r\no=- 789012 ...",
        }
        signaling.relay_message("alice", answer)

        # Alice receives answer
        alice_data = alice_resp.wfile.getvalue().decode("utf-8")
        assert "answer" in alice_data
        assert "bob" in alice_data

    def test_ice_candidate_exchange(self, signaling):
        """Simulate ICE candidate exchange."""
        alice_resp = MagicMock()
        alice_resp.wfile = BytesIO()

        bob_resp = MagicMock()
        bob_resp.wfile = BytesIO()

        signaling.register_client("alice", alice_resp)
        signaling.register_client("bob", bob_resp)

        # Exchange multiple ICE candidates
        for i in range(3):
            candidate = {
                "type": "candidate",
                "source": "alice",
                "target": "bob",
                "candidate": f"candidate:{i} udp ...",
            }
            signaling.relay_message("bob", candidate)

        bob_data = bob_resp.wfile.getvalue().decode("utf-8")
        assert bob_data.count("candidate") >= 3

    def test_late_joiner_receives_buffered(self, signaling):
        """Late joining peer should receive buffered messages."""
        # Alice connects first
        alice_resp = MagicMock()
        alice_resp.wfile = BytesIO()
        signaling.register_client("alice", alice_resp)

        # Alice sends offer to Bob (not yet connected)
        signaling.relay_message(
            "bob", {"type": "offer", "source": "alice", "target": "bob"}
        )

        # Alice sends candidates to Bob
        for i in range(2):
            signaling.relay_message(
                "bob", {"type": "candidate", "source": "alice", "candidate": f"ice-{i}"}
            )

        # Verify buffered
        status = signaling.get_status()
        assert status["buffered_messages"]["bob"] == 3

        # Bob finally connects
        bob_resp = MagicMock()
        bob_resp.wfile = BytesIO()
        signaling.register_client("bob", bob_resp)

        # Bob should receive all buffered messages
        bob_data = bob_resp.wfile.getvalue().decode("utf-8")
        assert "offer" in bob_data
        assert "ice-0" in bob_data
        assert "ice-1" in bob_data

        # Buffer should be cleared
        status = signaling.get_status()
        assert "bob" not in status["buffered_messages"]


class TestRunServer:
    """Tests for server startup function."""

    def test_run_server_background(self):
        """Should start server in background mode."""
        server = run_server(port=3099, background=True)

        try:
            assert server is not None
        finally:
            server.shutdown()


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
