"""Unit tests for KeycloakManager

Tests KeycloakManager functionality with mocked Keycloak Admin API.
"""

import pytest
from unittest.mock import Mock, patch, MagicMock
from keycloak.exceptions import KeycloakError

from blackant.auth.keycloak_manager import KeycloakManager
from blackant.config.keycloak_admin_config import KeycloakAdminConfig
from blackant.exceptions import (
    KeycloakConnectionError,
    KeycloakAuthenticationError,
    RoleAssignmentError
)


@pytest.fixture
def mock_config():
    """Mock KeycloakAdminConfig"""
    return KeycloakAdminConfig(
        server_url="https://keycloak.test.com/auth",
        realm_name="test-realm",
        client_id="test-client",
        client_secret="test-secret-1234567890",
        verify_ssl=False,
        timeout=10
    )


@pytest.fixture
def mock_keycloak_admin():
    """Mock KeycloakAdmin instance"""
    with patch('blackant.auth.keycloak_manager.KeycloakAdmin') as mock:
        admin = MagicMock()
        mock.return_value = admin
        yield admin


def test_initialization_success(mock_config, mock_keycloak_admin):
    """Test successful KeycloakManager initialization"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []

    # Execute
    manager = KeycloakManager(mock_config)

    # Assert
    assert manager.config == mock_config
    mock_keycloak_admin.get_realm_roles.assert_called_once()


def test_initialization_connection_error(mock_config, mock_keycloak_admin):
    """Test initialization fails on connection error"""
    # Setup
    mock_keycloak_admin.get_realm_roles.side_effect = Exception("Connection refused")

    # Execute & Assert
    with pytest.raises(KeycloakConnectionError):
        KeycloakManager(mock_config)


def test_get_user_roles_success(mock_config, mock_keycloak_admin):
    """Test getting user roles"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []
    mock_keycloak_admin.get_realm_roles_of_user.return_value = [
        {'id': '1', 'name': 'default-roles'},
        {'id': '2', 'name': 'science_module'}
    ]

    manager = KeycloakManager(mock_config)

    # Execute
    roles = manager.get_user_roles("user-uuid")

    # Assert
    assert len(roles) == 2
    assert roles[0]['name'] == 'default-roles'
    assert roles[1]['name'] == 'science_module'


def test_has_role_true(mock_config, mock_keycloak_admin):
    """Test has_role returns True when user has role"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []
    mock_keycloak_admin.get_realm_roles_of_user.return_value = [
        {'id': '1', 'name': 'science_module'}
    ]

    manager = KeycloakManager(mock_config)

    # Execute
    has_role = manager.has_role("user-uuid", "science_module")

    # Assert
    assert has_role is True


def test_has_role_false(mock_config, mock_keycloak_admin):
    """Test has_role returns False when user doesn't have role"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []
    mock_keycloak_admin.get_realm_roles_of_user.return_value = [
        {'id': '1', 'name': 'other-role'}
    ]

    manager = KeycloakManager(mock_config)

    # Execute
    has_role = manager.has_role("user-uuid", "science_module")

    # Assert
    assert has_role is False


def test_assign_role_success(mock_config, mock_keycloak_admin):
    """Test successful role assignment"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []
    mock_keycloak_admin.get_realm_roles_of_user.return_value = []
    mock_keycloak_admin.get_realm_role.return_value = {
        'id': '2',
        'name': 'science_module'
    }
    mock_keycloak_admin.assign_realm_roles.return_value = None

    manager = KeycloakManager(mock_config)

    # Execute
    success = manager.assign_role_to_user("user-uuid", "science_module")

    # Assert
    assert success is True
    mock_keycloak_admin.assign_realm_roles.assert_called_once()


def test_assign_role_idempotent(mock_config, mock_keycloak_admin):
    """Test idempotent role assignment (user already has role)"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []
    mock_keycloak_admin.get_realm_roles_of_user.return_value = [
        {'id': '2', 'name': 'science_module'}
    ]

    manager = KeycloakManager(mock_config)

    # Execute
    success = manager.assign_role_to_user("user-uuid", "science_module", idempotent=True)

    # Assert
    assert success is True
    mock_keycloak_admin.assign_realm_roles.assert_not_called()  # Skipped


def test_assign_role_not_exists(mock_config, mock_keycloak_admin):
    """Test role assignment fails when role doesn't exist"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []
    mock_keycloak_admin.get_realm_roles_of_user.return_value = []
    mock_keycloak_admin.get_realm_role.return_value = None  # Role not found

    manager = KeycloakManager(mock_config)

    # Execute & Assert
    with pytest.raises(RoleAssignmentError, match="does not exist"):
        manager.assign_role_to_user("user-uuid", "nonexistent-role")


def test_remove_role_success(mock_config, mock_keycloak_admin):
    """Test successful role removal"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []
    mock_keycloak_admin.get_realm_roles_of_user.return_value = [
        {'id': '2', 'name': 'science_module'}
    ]
    mock_keycloak_admin.get_realm_role.return_value = {
        'id': '2',
        'name': 'science_module'
    }
    mock_keycloak_admin.delete_realm_roles_of_user.return_value = None

    manager = KeycloakManager(mock_config)

    # Execute
    success = manager.remove_role_from_user("user-uuid", "science_module")

    # Assert
    assert success is True
    mock_keycloak_admin.delete_realm_roles_of_user.assert_called_once()


def test_remove_role_user_doesnt_have_it(mock_config, mock_keycloak_admin):
    """Test role removal when user doesn't have the role"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []
    mock_keycloak_admin.get_realm_roles_of_user.return_value = []

    manager = KeycloakManager(mock_config)

    # Execute
    success = manager.remove_role_from_user("user-uuid", "science_module")

    # Assert
    assert success is False
    mock_keycloak_admin.delete_realm_roles_of_user.assert_not_called()


def test_get_users_with_role_success(mock_config, mock_keycloak_admin):
    """Test getting users with a specific role"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []
    mock_keycloak_admin.get_realm_role.return_value = {
        'id': '2',
        'name': 'science_module'
    }
    mock_keycloak_admin.get_realm_role_members.return_value = [
        {'id': 'user1', 'username': 'alice'},
        {'id': 'user2', 'username': 'bob'}
    ]

    manager = KeycloakManager(mock_config)

    # Execute
    users = manager.get_users_with_role("science_module")

    # Assert
    assert len(users) == 2
    assert users[0]['username'] == 'alice'


def test_health_check_success(mock_config, mock_keycloak_admin):
    """Test health check success"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []

    manager = KeycloakManager(mock_config)

    # Execute
    healthy = manager.health_check()

    # Assert
    assert healthy is True


def test_health_check_failure(mock_config, mock_keycloak_admin):
    """Test health check failure"""
    # Setup
    mock_keycloak_admin.get_realm_roles.side_effect = [
        [],  # First call (initialization)
        Exception("Connection lost")  # Second call (health check)
    ]

    manager = KeycloakManager(mock_config)

    # Execute
    healthy = manager.health_check()

    # Assert
    assert healthy is False


def test_get_user_roles_keycloak_error(mock_config, mock_keycloak_admin):
    """Test get_user_roles handles Keycloak errors"""
    # Setup
    mock_keycloak_admin.get_realm_roles.return_value = []
    mock_keycloak_admin.get_realm_roles_of_user.side_effect = KeycloakError("API error")

    manager = KeycloakManager(mock_config)

    # Execute & Assert
    with pytest.raises(RoleAssignmentError):
        manager.get_user_roles("user-uuid")
