"""Unit tests for PIM enumerator."""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from azure_discovery.enumerators.pim import PIMEnumerator
from azure_discovery.adt_types.models import ResourceNode


@pytest.fixture
def mock_credential():
    """Create a mock Azure credential."""
    credential = AsyncMock()
    token = MagicMock()
    token.token = "mock_token_12345"
    credential.get_token = AsyncMock(return_value=token)
    return credential


@pytest.fixture
def pim_enumerator(mock_credential):
    """Create a PIM enumerator with mock credential."""
    return PIMEnumerator(
        credential=mock_credential,
        graph_endpoint="https://graph.microsoft.com",
        resource_manager_endpoint="https://management.azure.com",
    )


@pytest.mark.asyncio
async def test_enumerate_entra_role_eligibilities_empty(pim_enumerator):
    """Test Entra role eligibility enumeration with no results."""
    with patch("aiohttp.ClientSession") as mock_session_class:
        mock_session = AsyncMock()
        mock_session_class.return_value.__aenter__.return_value = mock_session

        # Mock API response with empty results
        mock_response = AsyncMock()
        mock_response.status = 200
        mock_response.json = AsyncMock(return_value={"value": []})
        mock_session.get.return_value.__aenter__.return_value = mock_response

        eligibilities = await pim_enumerator.enumerate_entra_role_eligibilities()

        assert isinstance(eligibilities, list)
        assert len(eligibilities) == 0


@pytest.mark.asyncio
async def test_enumerate_entra_role_eligibilities_with_results(pim_enumerator):
    """Test Entra role eligibility enumeration with results."""
    with patch("aiohttp.ClientSession") as mock_session_class:
        mock_session = AsyncMock()
        mock_session_class.return_value.__aenter__.return_value = mock_session

        # Mock API response with sample eligibility
        mock_response = AsyncMock()
        mock_response.status = 200
        mock_response.json = AsyncMock(
            return_value={
                "value": [
                    {
                        "id": "eligible-123",
                        "principalId": "user-guid-123",
                        "roleDefinitionId": "role-guid-456",
                        "directoryScopeId": "/",
                        "memberType": "Direct",
                        "status": "Provisioned",
                        "scheduleInfo": {
                            "startDateTime": "2026-01-01T00:00:00Z",
                            "expiration": {
                                "endDateTime": "2026-12-31T23:59:59Z",
                                "type": "AfterDateTime",
                            },
                        },
                    }
                ]
            }
        )
        mock_session.get.return_value.__aenter__.return_value = mock_response

        eligibilities = await pim_enumerator.enumerate_entra_role_eligibilities()

        assert isinstance(eligibilities, list)
        assert len(eligibilities) == 1
        assert eligibilities[0].type == "Microsoft.Graph.PIM/roleEligibilitySchedules"
        assert eligibilities[0].properties["principalId"] == "user-guid-123"
        assert eligibilities[0].properties["eligibilityType"] == "EntraRole"


@pytest.mark.asyncio
async def test_enumerate_azure_resource_eligibilities_empty(pim_enumerator):
    """Test Azure resource eligibility enumeration with no results."""
    with patch("aiohttp.ClientSession") as mock_session_class:
        mock_session = AsyncMock()
        mock_session_class.return_value.__aenter__.return_value = mock_session

        # Mock API response with empty results
        mock_response = AsyncMock()
        mock_response.status = 200
        mock_response.json = AsyncMock(return_value={"value": []})
        mock_session.get.return_value.__aenter__.return_value = mock_response

        eligibilities = await pim_enumerator.enumerate_azure_resource_role_eligibilities(
            subscription_ids=["sub-123"]
        )

        assert isinstance(eligibilities, list)
        assert len(eligibilities) == 0


@pytest.mark.asyncio
async def test_enumerate_azure_resource_eligibilities_with_results(pim_enumerator):
    """Test Azure resource eligibility enumeration with results."""
    with patch("aiohttp.ClientSession") as mock_session_class:
        mock_session = AsyncMock()
        mock_session_class.return_value.__aenter__.return_value = mock_session

        # Mock API response with sample eligibility
        mock_response = AsyncMock()
        mock_response.status = 200
        mock_response.json = AsyncMock(
            return_value={
                "value": [
                    {
                        "id": "/subscriptions/sub-123/providers/Microsoft.Authorization/roleEligibilitySchedules/eligible-789",
                        "name": "eligible-789",
                        "properties": {
                            "scope": "/subscriptions/sub-123",
                            "roleDefinitionId": "/subscriptions/sub-123/providers/Microsoft.Authorization/roleDefinitions/role-owner",
                            "principalId": "user-guid-456",
                            "principalType": "User",
                            "memberType": "Direct",
                            "status": "Provisioned",
                            "startDateTime": "2026-01-01T00:00:00Z",
                            "endDateTime": "2026-12-31T23:59:59Z",
                        },
                    }
                ]
            }
        )
        mock_session.get.return_value.__aenter__.return_value = mock_response

        eligibilities = await pim_enumerator.enumerate_azure_resource_role_eligibilities(
            subscription_ids=["sub-123"]
        )

        assert isinstance(eligibilities, list)
        assert len(eligibilities) == 1
        assert eligibilities[0].type == "Microsoft.Authorization/roleEligibilitySchedules"
        assert eligibilities[0].properties["principalId"] == "user-guid-456"
        assert eligibilities[0].properties["eligibilityType"] == "AzureResource"
        assert eligibilities[0].subscription_id == "sub-123"


@pytest.mark.asyncio
async def test_enumerate_handles_403_gracefully(pim_enumerator):
    """Test that 403 errors are handled gracefully."""
    with patch("aiohttp.ClientSession") as mock_session_class:
        mock_session = AsyncMock()
        mock_session_class.return_value.__aenter__.return_value = mock_session

        # Mock API response with 403 Forbidden
        mock_response = AsyncMock()
        mock_response.status = 403
        mock_session.get.return_value.__aenter__.return_value = mock_response

        eligibilities = await pim_enumerator.enumerate_entra_role_eligibilities()

        # Should return empty list instead of raising exception
        assert isinstance(eligibilities, list)
        assert len(eligibilities) == 0


@pytest.mark.asyncio
async def test_scope_filter_applies_correctly(pim_enumerator):
    """Test that scope filtering works correctly."""
    with patch("aiohttp.ClientSession") as mock_session_class:
        mock_session = AsyncMock()
        mock_session_class.return_value.__aenter__.return_value = mock_session

        # Mock API response with multiple eligibilities at different scopes
        mock_response = AsyncMock()
        mock_response.status = 200
        mock_response.json = AsyncMock(
            return_value={
                "value": [
                    {
                        "id": "/subscriptions/sub-123/providers/Microsoft.Authorization/roleEligibilitySchedules/eligible-1",
                        "name": "eligible-1",
                        "properties": {
                            "scope": "/subscriptions/sub-123/resourceGroups/rg-prod",
                            "roleDefinitionId": "role-1",
                            "principalId": "user-1",
                            "principalType": "User",
                        },
                    },
                    {
                        "id": "/subscriptions/sub-123/providers/Microsoft.Authorization/roleEligibilitySchedules/eligible-2",
                        "name": "eligible-2",
                        "properties": {
                            "scope": "/subscriptions/sub-123/resourceGroups/rg-dev",
                            "roleDefinitionId": "role-2",
                            "principalId": "user-2",
                            "principalType": "User",
                        },
                    },
                ]
            }
        )
        mock_session.get.return_value.__aenter__.return_value = mock_response

        # Filter to only prod resource group
        eligibilities = await pim_enumerator.enumerate_azure_resource_role_eligibilities(
            subscription_ids=["sub-123"],
            scope_filter=["/subscriptions/sub-123/resourceGroups/rg-prod"],
        )

        # Should only get the prod eligibility
        assert isinstance(eligibilities, list)
        assert len(eligibilities) == 1
        assert "rg-prod" in eligibilities[0].properties["scope"]


def test_matches_scope():
    """Test scope matching logic."""
    pim = PIMEnumerator(
        credential=AsyncMock(),
        graph_endpoint="https://graph.microsoft.com",
        resource_manager_endpoint="https://management.azure.com",
    )

    # Test exact prefix match
    assert pim._matches_scope(
        "/subscriptions/sub-123/resourceGroups/rg-prod",
        ["/subscriptions/sub-123/resourceGroups/rg-prod"],
    )

    # Test parent scope match
    assert pim._matches_scope(
        "/subscriptions/sub-123/resourceGroups/rg-prod/providers/Microsoft.Storage/storageAccounts/account1",
        ["/subscriptions/sub-123/resourceGroups/rg-prod"],
    )

    # Test no match
    assert not pim._matches_scope(
        "/subscriptions/sub-123/resourceGroups/rg-dev",
        ["/subscriptions/sub-123/resourceGroups/rg-prod"],
    )

    # Test case insensitive match
    assert pim._matches_scope(
        "/subscriptions/SUB-123/resourceGroups/RG-PROD",
        ["/subscriptions/sub-123/resourceGroups/rg-prod"],
    )
