"""Role gate regression tests — require_role() blocks unauthorized access.

Patches ui.guard.st so no Streamlit server is needed.
st.stop() and st.switch_page() raise SystemExit in tests to halt execution,
mirroring what Streamlit does at runtime (StopException / RerunException).
"""
from __future__ import annotations

from unittest.mock import patch

import pytest

from ui.guard import require_role


@pytest.fixture(autouse=True)
def mock_st():
    """Replace Streamlit side-effects for every test in this module."""
    with patch("ui.guard.st") as m:
        m.session_state = {}  # real dict so .get() works
        m.stop.side_effect = SystemExit("st.stop")
        m.switch_page.side_effect = SystemExit("st.switch_page")
        yield m


# ---------------------------------------------------------------------------
# Unauthenticated (no session role set)
# ---------------------------------------------------------------------------

def test_unauthenticated_redirects_to_login(mock_st):
    """No user_role in session → warning + redirect to login."""
    mock_st.session_state.clear()
    with pytest.raises(SystemExit):
        require_role("Admin")
    mock_st.warning.assert_called_once()
    mock_st.switch_page.assert_called_once_with("pages/login.py")


def test_none_role_redirects_to_login(mock_st):
    """Explicit None role → treated same as unauthenticated."""
    mock_st.session_state["user_role"] = None
    with pytest.raises(SystemExit):
        require_role("Expert")
    mock_st.switch_page.assert_called_once()


# ---------------------------------------------------------------------------
# Wrong role — should be blocked
# ---------------------------------------------------------------------------

def test_company_rep_blocked_from_admin(mock_st):
    mock_st.session_state["user_role"] = "CompanyRep"
    with pytest.raises(SystemExit):
        require_role("Admin")
    mock_st.error.assert_called_once()
    mock_st.stop.assert_called_once()


def test_expert_blocked_from_admin(mock_st):
    mock_st.session_state["user_role"] = "Expert"
    with pytest.raises(SystemExit):
        require_role("Admin")
    mock_st.stop.assert_called_once()


def test_company_rep_blocked_from_expert_only(mock_st):
    mock_st.session_state["user_role"] = "CompanyRep"
    with pytest.raises(SystemExit):
        require_role("Expert")
    mock_st.stop.assert_called_once()


def test_admin_blocked_from_expert_only_page(mock_st):
    """Admin is not automatically granted Expert-only access."""
    mock_st.session_state["user_role"] = "Admin"
    with pytest.raises(SystemExit):
        require_role("Expert")
    mock_st.stop.assert_called_once()


# ---------------------------------------------------------------------------
# Correct role — should pass without stopping
# ---------------------------------------------------------------------------

def test_admin_passes_admin_gate(mock_st):
    mock_st.session_state["user_role"] = "Admin"
    require_role("Admin")  # must not raise
    mock_st.stop.assert_not_called()
    mock_st.switch_page.assert_not_called()


def test_expert_passes_expert_gate(mock_st):
    mock_st.session_state["user_role"] = "Expert"
    require_role("Expert")
    mock_st.stop.assert_not_called()


def test_company_rep_passes_company_rep_gate(mock_st):
    mock_st.session_state["user_role"] = "CompanyRep"
    require_role("CompanyRep")
    mock_st.stop.assert_not_called()


# ---------------------------------------------------------------------------
# Multi-role gates (e.g. results page allows Expert + Admin + CompanyRep)
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("role", ["Expert", "Admin", "CompanyRep"])
def test_all_roles_pass_multi_role_gate(mock_st, role):
    mock_st.session_state["user_role"] = role
    require_role("Expert", "Admin", "CompanyRep")
    mock_st.stop.assert_not_called()


@pytest.mark.parametrize("role", ["Expert", "Admin"])
def test_expert_and_admin_pass_history_gate(mock_st, role):
    mock_st.session_state["user_role"] = role
    require_role("CompanyRep", "Admin", "Expert")
    mock_st.stop.assert_not_called()
