"""Integration tests for automatic role assignment

Tests full workflow with mocked/real Keycloak instance.
"""

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

from blackant.services.registry import ServiceRegistry
from blackant.auth.blackant_auth import BlackAntAuth
from blackant.auth.role_assignment import RoleAssignmentManager
from blackant.auth.keycloak_manager import KeycloakManager
from blackant.config.keycloak_admin_config import KeycloakAdminConfig


@pytest.fixture
def mock_auth():
    """Mock BlackAntAuth"""
    auth = Mock(spec=BlackAntAuth)
    auth.token_store = Mock()

    # Create a valid JWT token
    payload = {
        'sub': 'test-user-uuid-12345',
        'preferred_username': 'testuser',
        'email': 'test@blackant.app'
    }
    auth.token_store.user_token = pyjwt.encode(payload, 'secret', algorithm='HS256')
    auth.get_token.return_value = "Bearer mock-token"

    return auth


@pytest.fixture
def mock_keycloak_manager():
    """Mock KeycloakManager for integration tests"""
    with patch('blackant.auth.role_assignment.get_keycloak_manager') as mock_get:
        manager = Mock(spec=KeycloakManager)
        manager.has_role.return_value = False
        manager.assign_role_to_user.return_value = True
        mock_get.return_value = manager
        yield manager


@pytest.fixture
def mock_http_client():
    """Mock HTTPClient for service registration"""
    with patch('blackant.services.registry.HTTPClient') as mock_client_class:
        # Create mock instance
        mock_instance = MagicMock()

        # Mock successful service registration response
        response = Mock()
        response.status_code = 200
        response.json.return_value = {
            'service_id': 'test-service-12345',
            'id': 'test-service-12345'
        }
        response.raise_for_status = Mock()

        # Setup the mock to return response when send_request is called
        mock_instance.send_request.return_value = response

        # Make the class constructor return our mock instance
        mock_client_class.return_value = mock_instance

        yield mock_instance


@pytest.fixture
def mock_service_dao():
    """Mock ServiceManagerDAO"""
    with patch('blackant.services.registry.ServiceManagerDAO') as mock_dao:
        # Make it None to avoid Docker initialization
        mock_dao.return_value = None
        yield mock_dao


@pytest.mark.integration
def test_service_registration_triggers_role_assignment(mock_auth, mock_keycloak_manager, mock_http_client, mock_service_dao):
    """Test that service registration triggers automatic role assignment"""
    # Setup
    registry = ServiceRegistry(auth=mock_auth)

    service_data = {
        'name': 'test-calculation',
        'namespace': 'systemdevelopers',
        'image': 'env.blackant.app/test:latest',
        'type': 'calculation'
    }

    # Execute
    service_id = registry.register(service_data)

    # Assert
    assert service_id == 'test-service-12345'

    # Verify role assignment was called
    mock_keycloak_manager.has_role.assert_called_once_with('test-user-uuid-12345', 'science_module')
    mock_keycloak_manager.assign_role_to_user.assert_called_once_with(
        user_id='test-user-uuid-12345',
        role_name='science_module',
        idempotent=True
    )


@pytest.mark.integration
def test_service_registration_idempotent_role_assignment(mock_auth, mock_keycloak_manager, mock_http_client, mock_service_dao):
    """Test that role assignment is idempotent (user already has role)"""
    # Setup - user already has role
    # Configure mock BEFORE creating registry (so singleton gets mocked instance)
    mock_keycloak_manager.has_role.return_value = True

    # Reset the singleton before creating registry
    with patch('blackant.auth.role_assignment._role_assignment_manager', None):
        registry = ServiceRegistry(auth=mock_auth)

        service_data = {
            'name': 'test-calculation',
            'namespace': 'systemdevelopers'
        }

        # Execute
        service_id = registry.register(service_data)

        # Assert
        assert service_id == 'test-service-12345'

        # Verify role check was called but assignment was skipped
        mock_keycloak_manager.has_role.assert_called_once()
        mock_keycloak_manager.assign_role_to_user.assert_not_called()


@pytest.mark.integration
def test_service_registration_succeeds_despite_role_assignment_failure(mock_auth, mock_keycloak_manager, mock_http_client, mock_service_dao):
    """Test that service registration succeeds even if role assignment fails"""
    # Setup - role assignment will fail
    mock_keycloak_manager.assign_role_to_user.return_value = False

    registry = ServiceRegistry(auth=mock_auth)

    service_data = {
        'name': 'test-calculation',
        'namespace': 'systemdevelopers'
    }

    # Execute - should not raise exception
    service_id = registry.register(service_data)

    # Assert - service was still registered successfully
    assert service_id == 'test-service-12345'


@pytest.mark.integration
def test_service_registration_without_user_token(mock_auth, mock_keycloak_manager, mock_http_client, mock_service_dao):
    """Test that service registration works without user token (skips role assignment)"""
    # Setup - no user token
    mock_auth.token_store.user_token = None

    registry = ServiceRegistry(auth=mock_auth)

    service_data = {
        'name': 'test-calculation',
        'namespace': 'systemdevelopers'
    }

    # Execute
    service_id = registry.register(service_data)

    # Assert
    assert service_id == 'test-service-12345'

    # Verify role assignment was not attempted
    mock_keycloak_manager.has_role.assert_not_called()
    mock_keycloak_manager.assign_role_to_user.assert_not_called()


@pytest.mark.integration
def test_role_assignment_disabled_via_env_var(mock_auth, mock_keycloak_manager, mock_http_client, mock_service_dao):
    """Test that role assignment can be disabled via environment variable"""
    # Setup - disable automatic role assignment
    with patch.dict(os.environ, {'BLACKANT_AUTO_ASSIGN_ENABLED': 'false'}):
        registry = ServiceRegistry(auth=mock_auth)

        service_data = {
            'name': 'test-calculation',
            'namespace': 'systemdevelopers'
        }

        # Execute
        service_id = registry.register(service_data)

        # Assert
        assert service_id == 'test-service-12345'

        # Verify role assignment was not attempted
        mock_keycloak_manager.has_role.assert_not_called()
        mock_keycloak_manager.assign_role_to_user.assert_not_called()


@pytest.mark.integration
def test_role_assignment_with_metadata(mock_auth, mock_keycloak_manager, mock_http_client, mock_service_dao):
    """Test that role assignment includes service metadata"""
    # Setup
    registry = ServiceRegistry(auth=mock_auth)

    service_data = {
        'name': 'test-calculation',
        'namespace': 'systemdevelopers',
        'image': 'env.blackant.app/test:v1.2.3',
        'type': 'calculation'
    }

    # Execute
    with patch('blackant.auth.role_assignment.RoleAssignmentManager.assign_role_after_publish') as mock_assign:
        mock_assign.return_value = True

        service_id = registry.register(service_data)

        # Assert
        assert service_id == 'test-service-12345'

        # Verify metadata was passed
        mock_assign.assert_called_once()
        call_kwargs = mock_assign.call_args[1]
        assert call_kwargs['service_name'] == 'test-calculation'
        assert 'metadata' in call_kwargs
        assert call_kwargs['metadata']['image'] == 'env.blackant.app/test:v1.2.3'
        assert call_kwargs['metadata']['type'] == 'calculation'


@pytest.mark.integration
def test_full_workflow_end_to_end(mock_auth, mock_keycloak_manager, mock_http_client, mock_service_dao):
    """Test complete end-to-end workflow from service registration to role assignment"""
    # Track all interactions
    interactions = []

    def track_has_role(user_id, role_name):
        interactions.append(f"has_role({user_id}, {role_name})")
        return False

    def track_assign_role(user_id, role_name, idempotent):
        interactions.append(f"assign_role({user_id}, {role_name}, {idempotent})")
        return True

    # Configure mock side effects BEFORE creating registry
    mock_keycloak_manager.has_role.side_effect = track_has_role
    mock_keycloak_manager.assign_role_to_user.side_effect = track_assign_role

    # Reset the singleton before creating registry
    with patch('blackant.auth.role_assignment._role_assignment_manager', None):
        registry = ServiceRegistry(auth=mock_auth)

        service_data = {
            'name': 'sentiment-analysis',
            'namespace': 'systemdevelopers',
            'image': 'env.blackant.app/systemdevelopers/sentiment-analysis:v2.1.0',
            'type': 'calculation'
        }

        # Execute
        service_id = registry.register(service_data)

        # Assert
        assert service_id == 'test-service-12345'

        # Verify correct sequence of operations
        assert len(interactions) == 2
        assert interactions[0] == "has_role(test-user-uuid-12345, science_module)"
        assert interactions[1] == "assign_role(test-user-uuid-12345, science_module, True)"


@pytest.mark.integration
@pytest.mark.skipif(
    not os.getenv('KEYCLOAK_SERVER_URL'),
    reason="Keycloak not configured for integration tests"
)
def test_real_keycloak_integration():
    """Test with real Keycloak instance (requires configuration)

    This test is skipped unless KEYCLOAK_SERVER_URL is configured.
    Use this for manual testing with a real Keycloak instance.
    """
    # This would use real Keycloak configuration
    config = KeycloakAdminConfig.from_env()
    manager = KeycloakManager(config)

    # Test health check
    assert manager.health_check() is True

    # Note: Actual role assignment tests would require a test user
    # and proper cleanup, which is beyond the scope of unit tests
