"""Tests for SessionRegistry."""

import pytest
from uuid import uuid4
from unittest.mock import MagicMock

from frisk_sdk.core.session_registry import SessionRegistry, SessionNotFoundError


@pytest.fixture
def registry():
    """Create a fresh registry for each test."""
    reg = SessionRegistry()
    reg.clear()
    return reg


@pytest.mark.unit
def test_session_registry_is_singleton():
    """SessionRegistry should be a singleton."""
    reg1 = SessionRegistry()
    reg2 = SessionRegistry()
    assert reg1 is reg2


@pytest.mark.unit
def test_register_and_get_session(registry):
    """Test basic register and get operations."""
    session_id = str(uuid4())
    mock_session = MagicMock(name="FriskSession")

    registry.register(session_id, mock_session)
    retrieved = registry.get(session_id)

    assert retrieved is mock_session


@pytest.mark.unit
def test_get_with_string_uuid(registry):
    """Test getting a session using string representation of UUID."""
    session_id = str(uuid4())
    mock_session = MagicMock(name="FriskSession")

    registry.register(session_id, mock_session)
    retrieved = registry.get(str(session_id))

    assert retrieved is mock_session


@pytest.mark.unit
def test_get_nonexistent_session_raises_error(registry):
    """Test that getting a nonexistent session raises SessionNotFoundError."""
    session_id = str(uuid4())

    with pytest.raises(SessionNotFoundError) as excinfo:
        registry.get(session_id)

    assert str(session_id) in str(excinfo.value)


@pytest.mark.unit
def test_get_with_invalid_string_raises_error(registry):
    """Test that getting with an invalid UUID string raises SessionNotFoundError."""
    with pytest.raises(SessionNotFoundError) as excinfo:
        registry.get("not-a-uuid")

    assert "not-a-uuid" in str(excinfo.value)


@pytest.mark.unit
def test_unregister_session(registry):
    """Test unregistering a session."""
    session_id = str(uuid4())
    mock_session = MagicMock(name="FriskSession")

    registry.register(session_id, mock_session)
    registry.unregister(session_id)

    with pytest.raises(SessionNotFoundError):
        registry.get(session_id)


@pytest.mark.unit
def test_unregister_nonexistent_session_is_noop(registry):
    """Test that unregistering a nonexistent session doesn't raise an error."""
    session_id = str(uuid4())
    # Should not raise
    registry.unregister(session_id)


@pytest.mark.unit
def test_clear_removes_all_sessions(registry):
    """Test that clear removes all sessions."""
    session_id1 = uuid4()
    session_id2 = uuid4()
    mock_session1 = MagicMock(name="Session1")
    mock_session2 = MagicMock(name="Session2")

    registry.register(session_id1, mock_session1)
    registry.register(session_id2, mock_session2)

    registry.clear()

    with pytest.raises(SessionNotFoundError):
        registry.get(session_id1)

    with pytest.raises(SessionNotFoundError):
        registry.get(session_id2)


@pytest.mark.unit
def test_register_same_id_overwrites(registry):
    """Test that registering the same ID twice overwrites the first session."""
    session_id = str(uuid4())
    mock_session1 = MagicMock(name="Session1")
    mock_session2 = MagicMock(name="Session2")

    registry.register(session_id, mock_session1)
    registry.register(session_id, mock_session2)

    retrieved = registry.get(session_id)
    assert retrieved is mock_session2


@pytest.mark.unit
def test_multiple_sessions_isolated(registry):
    """Test that multiple sessions can coexist without interfering."""
    session_id1 = uuid4()
    session_id2 = uuid4()
    mock_session1 = MagicMock(name="Session1")
    mock_session2 = MagicMock(name="Session2")

    registry.register(session_id1, mock_session1)
    registry.register(session_id2, mock_session2)

    assert registry.get(session_id1) is mock_session1
    assert registry.get(session_id2) is mock_session2


@pytest.mark.integration
def test_thread_safety(registry):
    """Test that registry operations are thread-safe."""
    import threading

    session_ids = [uuid4() for _ in range(10)]
    mock_sessions = [MagicMock(name=f"Session{i}") for i in range(10)]

    def register_session(sid, session):
        registry.register(sid, session)

    threads = [
        threading.Thread(target=register_session, args=(sid, sess))
        for sid, sess in zip(session_ids, mock_sessions)
    ]

    for t in threads:
        t.start()

    for t in threads:
        t.join()

    # Verify all sessions were registered
    for sid, sess in zip(session_ids, mock_sessions):
        assert registry.get(sid) is sess


@pytest.mark.unit
def test_session_not_found_error_attributes():
    """Test SessionNotFoundError has correct attributes."""
    session_id = str(uuid4())
    error = SessionNotFoundError(session_id)

    assert error.session_id == session_id
    assert str(session_id) in str(error)
    assert "Session not found" in str(error)
