"""Unit tests for RoleAssignmentManager

Tests role assignment orchestration logic.
"""

import pytest
from unittest.mock import Mock, patch
import jwt as pyjwt

from blackant.auth.role_assignment import RoleAssignmentManager
from blackant.exceptions import TokenValidationError, RoleAssignmentError


@pytest.fixture
def mock_keycloak_manager():
    """Mock KeycloakManager"""
    manager = Mock()
    manager.has_role.return_value = False
    manager.assign_role_to_user.return_value = True
    return manager


@pytest.fixture
def valid_jwt_token():
    """Generate valid JWT token for testing"""
    payload = {
        'sub': 'user-uuid-1234',
        'preferred_username': 'testuser',
        'email': 'test@company.com'
    }
    return pyjwt.encode(payload, 'secret', algorithm='HS256')


def test_extract_user_id_success(mock_keycloak_manager, valid_jwt_token):
    """Test successful user ID extraction from JWT"""
    # Setup
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    # Execute
    user_id = manager.extract_user_id_from_token(valid_jwt_token)

    # Assert
    assert user_id == 'user-uuid-1234'


def test_extract_user_id_invalid_token(mock_keycloak_manager):
    """Test user ID extraction fails on invalid token"""
    # Setup
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    # Execute & Assert
    with pytest.raises(TokenValidationError):
        manager.extract_user_id_from_token("invalid.token.here")


def test_extract_user_id_missing_sub_claim(mock_keycloak_manager):
    """Test user ID extraction fails when 'sub' claim is missing"""
    # Setup
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    # Create token without 'sub' claim
    payload = {'preferred_username': 'testuser'}
    token = pyjwt.encode(payload, 'secret', algorithm='HS256')

    # Execute & Assert
    with pytest.raises(TokenValidationError, match="does not contain 'sub' claim"):
        manager.extract_user_id_from_token(token)


def test_assign_role_success(mock_keycloak_manager, valid_jwt_token):
    """Test successful role assignment"""
    # Setup
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    # Execute
    success = manager.assign_role_after_publish(
        user_token=valid_jwt_token,
        service_name="test-service"
    )

    # Assert
    assert success is True
    mock_keycloak_manager.assign_role_to_user.assert_called_once()


def test_assign_role_already_exists(mock_keycloak_manager, valid_jwt_token):
    """Test role assignment when user already has role"""
    # Setup
    mock_keycloak_manager.has_role.return_value = True  # Already has role
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    # Execute
    success = manager.assign_role_after_publish(
        user_token=valid_jwt_token,
        service_name="test-service"
    )

    # Assert
    assert success is True
    mock_keycloak_manager.assign_role_to_user.assert_not_called()  # Skipped


def test_assign_role_invalid_token(mock_keycloak_manager):
    """Test role assignment gracefully handles invalid token"""
    # Setup
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    # Execute
    success = manager.assign_role_after_publish(
        user_token="invalid.token",
        service_name="test-service"
    )

    # Assert
    assert success is False  # Don't raise, just return False
    mock_keycloak_manager.assign_role_to_user.assert_not_called()


def test_assign_role_keycloak_error(mock_keycloak_manager, valid_jwt_token):
    """Test role assignment gracefully handles Keycloak errors"""
    # Setup
    mock_keycloak_manager.assign_role_to_user.side_effect = RoleAssignmentError("Keycloak error")
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    # Execute
    success = manager.assign_role_after_publish(
        user_token=valid_jwt_token,
        service_name="test-service"
    )

    # Assert
    assert success is False  # Gracefully handle error


def test_assign_role_unexpected_error(mock_keycloak_manager, valid_jwt_token):
    """Test role assignment gracefully handles unexpected errors"""
    # Setup
    mock_keycloak_manager.assign_role_to_user.side_effect = Exception("Unexpected error")
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    # Execute
    success = manager.assign_role_after_publish(
        user_token=valid_jwt_token,
        service_name="test-service"
    )

    # Assert
    assert success is False  # Gracefully handle error


def test_assign_role_with_metadata(mock_keycloak_manager, valid_jwt_token):
    """Test role assignment includes metadata in audit log"""
    # Setup
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    with patch.object(manager, '_audit_log') as mock_audit:
        # Execute
        manager.assign_role_after_publish(
            user_token=valid_jwt_token,
            service_name="test-service",
            metadata={'image': 'test:latest', 'version': '1.0.0'}
        )

        # Assert
        mock_audit.assert_called()
        call_kwargs = mock_audit.call_args[1]
        assert call_kwargs['action'] == 'role_assigned'
        assert call_kwargs['result'] == 'success'
        assert call_kwargs['metadata'] == {'image': 'test:latest', 'version': '1.0.0'}


def test_audit_log_on_error(mock_keycloak_manager):
    """Test audit log is created on error"""
    # Setup
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    with patch.object(manager, '_audit_log') as mock_audit:
        # Execute
        manager.assign_role_after_publish(
            user_token="invalid.token",
            service_name="test-service"
        )

        # Assert
        mock_audit.assert_called()
        call_kwargs = mock_audit.call_args[1]
        assert call_kwargs['result'] == 'token_error'
        assert 'error' in call_kwargs


def test_custom_role_name(mock_keycloak_manager, valid_jwt_token):
    """Test role assignment with custom role name"""
    # Setup
    manager = RoleAssignmentManager(
        keycloak_manager=mock_keycloak_manager,
        role_name="custom_role"
    )

    # Execute
    manager.assign_role_after_publish(
        user_token=valid_jwt_token,
        service_name="test-service"
    )

    # Assert
    mock_keycloak_manager.has_role.assert_called_with('user-uuid-1234', 'custom_role')


def test_audit_log_structure(mock_keycloak_manager, valid_jwt_token):
    """Test audit log contains all required fields"""
    # Setup
    manager = RoleAssignmentManager(keycloak_manager=mock_keycloak_manager)

    with patch.object(manager, '_audit_log') as mock_audit:
        # Execute
        manager.assign_role_after_publish(
            user_token=valid_jwt_token,
            service_name="test-service",
            metadata={'image': 'test:latest'}
        )

        # Assert
        call_kwargs = mock_audit.call_args[1]
        assert 'user_id' in call_kwargs
        assert 'service_name' in call_kwargs
        assert 'action' in call_kwargs
        assert 'result' in call_kwargs
        assert 'metadata' in call_kwargs
