"""Tests for AzureIAMAnalyzer using unittest.mock."""

from __future__ import annotations

import sys
from datetime import UTC, datetime, timedelta
from unittest.mock import MagicMock, patch

from complyform.core.cloud_intelligence.iam_analyzer import IAMAnalysisResult
from complyform.core.cloud_intelligence.iam_azure import AzureIAMAnalyzer

# Ensure Azure SDKs are mockable even when not installed.
_AZURE_MODULES = [
    "azure",
    "azure.identity",
    "azure.mgmt",
    "azure.mgmt.authorization",
    "azure.mgmt.monitor",
    "azure.core",
    "azure.core.exceptions",
]
for _mod in _AZURE_MODULES:
    if _mod not in sys.modules:
        sys.modules[_mod] = MagicMock()


def _make_analyzer(
    credentials: object | None = None,
) -> AzureIAMAnalyzer:
    return AzureIAMAnalyzer(
        cloud="azure",
        project_id="sub-123",
        credentials=credentials,
    )


def _mock_role_assignment(
    principal_id: str = "user-abc",
    role_definition_id: str = (
        "/subscriptions/sub/providers/Microsoft.Authorization/roleDefinitions/Owner"
    ),
    principal_type: str = "User",
) -> MagicMock:
    assignment = MagicMock()
    assignment.principal_id = principal_id
    assignment.role_definition_id = role_definition_id
    assignment.principal_type = principal_type
    return assignment


def _mock_activity_log_event(
    timestamp: datetime | None = None,
) -> MagicMock:
    event = MagicMock()
    event.event_timestamp = timestamp or datetime.now(tz=UTC)
    return event


class TestAzureIAMAnalyzerAnalyze:
    """Tests for AzureIAMAnalyzer.analyze()."""

    def test_role_assignments_with_activity(self) -> None:
        mock_auth_client = MagicMock()
        mock_monitor_client = MagicMock()

        assignment = _mock_role_assignment()
        mock_auth_client.role_assignments.list_for_subscription.return_value = [
            assignment,
        ]

        recent_event = _mock_activity_log_event(
            datetime.now(tz=UTC) - timedelta(days=10),
        )
        mock_monitor_client.activity_logs.list.return_value = [recent_event]

        mock_auth_cls = MagicMock(return_value=mock_auth_client)
        mock_monitor_cls = MagicMock(return_value=mock_monitor_client)

        analyzer = _make_analyzer(credentials=MagicMock())
        with (
            patch(
                "azure.mgmt.authorization.AuthorizationManagementClient",
                mock_auth_cls,
            ),
            patch(
                "azure.mgmt.monitor.MonitorManagementClient",
                mock_monitor_cls,
            ),
        ):
            result = analyzer.analyze()

        assert isinstance(result, IAMAnalysisResult)
        assert result.success is True
        assert len(result.findings) == 1
        assert result.findings[0].principal == "user-abc"
        assert result.findings[0].last_activity is not None

    def test_no_activity_90_days(self) -> None:
        mock_auth_client = MagicMock()
        mock_monitor_client = MagicMock()

        assignment = _mock_role_assignment(
            role_definition_id=("/subscriptions/sub/roleDefinitions/Contributor"),
        )
        mock_auth_client.role_assignments.list_for_subscription.return_value = [
            assignment,
        ]
        mock_monitor_client.activity_logs.list.return_value = []

        mock_auth_cls = MagicMock(return_value=mock_auth_client)
        mock_monitor_cls = MagicMock(return_value=mock_monitor_client)

        analyzer = _make_analyzer(credentials=MagicMock())
        with (
            patch(
                "azure.mgmt.authorization.AuthorizationManagementClient",
                mock_auth_cls,
            ),
            patch(
                "azure.mgmt.monitor.MonitorManagementClient",
                mock_monitor_cls,
            ),
        ):
            result = analyzer.analyze()

        assert result.success is True
        assert len(result.findings) == 1
        finding = result.findings[0]
        assert finding.last_activity is None
        # Unused Contributor -> HIGH risk, remove
        assert finding.risk_level == "HIGH"
        assert finding.recommended_role is None

    def test_graph_phase_skipped_on_import_error(self) -> None:
        mock_auth_client = MagicMock()
        mock_monitor_client = MagicMock()

        assignment = _mock_role_assignment()
        mock_auth_client.role_assignments.list_for_subscription.return_value = [
            assignment,
        ]
        mock_monitor_client.activity_logs.list.return_value = []

        mock_auth_cls = MagicMock(return_value=mock_auth_client)
        mock_monitor_cls = MagicMock(return_value=mock_monitor_client)

        analyzer = _make_analyzer(credentials=MagicMock())
        with (
            patch(
                "azure.mgmt.authorization.AuthorizationManagementClient",
                mock_auth_cls,
            ),
            patch(
                "azure.mgmt.monitor.MonitorManagementClient",
                mock_monitor_cls,
            ),
            patch.dict("sys.modules", {"msgraph": None}),
        ):
            result = analyzer.analyze()

        assert result.success is True
        # Phase 1 results still returned.
        assert len(result.findings) >= 1

    def test_graph_phase_appends_findings(self) -> None:
        mock_auth_client = MagicMock()
        mock_monitor_client = MagicMock()

        mock_auth_client.role_assignments.list_for_subscription.return_value = []
        mock_monitor_client.activity_logs.list.return_value = []

        mock_auth_cls = MagicMock(return_value=mock_auth_client)
        mock_monitor_cls = MagicMock(return_value=mock_monitor_client)

        # Mock Graph SDK.
        mock_graph = MagicMock()
        sp = MagicMock()
        sp.id = "sp-123"
        sp.display_name = "MyApp"
        role_assign = MagicMock()
        role_assign.resource_display_name = "Owner"
        sp.app_role_assignments = [role_assign]
        mock_graph_client = MagicMock()
        mock_graph_client.service_principals.get.return_value = MagicMock(
            value=[sp],
        )
        mock_graph.GraphServiceClient.return_value = mock_graph_client

        analyzer = _make_analyzer(credentials=MagicMock())
        with (
            patch(
                "azure.mgmt.authorization.AuthorizationManagementClient",
                mock_auth_cls,
            ),
            patch(
                "azure.mgmt.monitor.MonitorManagementClient",
                mock_monitor_cls,
            ),
            patch.dict("sys.modules", {"msgraph": mock_graph}),
        ):
            result = analyzer.analyze()

        assert result.success is True
        graph_findings = [f for f in result.findings if f.principal == "MyApp"]
        assert len(graph_findings) == 1

    def test_authentication_error_returns_success_false(self) -> None:
        analyzer = _make_analyzer(credentials=MagicMock())
        with patch(
            "azure.mgmt.authorization.AuthorizationManagementClient",
            side_effect=Exception("ClientAuthenticationError: auth failed"),
        ):
            result = analyzer.analyze()

        assert result.success is False
        assert result.error is not None

    def test_http_429_returns_success_false(self) -> None:
        mock_auth_client = MagicMock()
        error = Exception("HttpResponseError: 429")
        error.status_code = 429  # type: ignore[attr-defined]
        mock_auth_client.role_assignments.list_for_subscription.side_effect = error

        mock_auth_cls = MagicMock(return_value=mock_auth_client)
        mock_monitor_cls = MagicMock()

        analyzer = _make_analyzer(credentials=MagicMock())
        with (
            patch(
                "azure.mgmt.authorization.AuthorizationManagementClient",
                mock_auth_cls,
            ),
            patch(
                "azure.mgmt.monitor.MonitorManagementClient",
                mock_monitor_cls,
            ),
        ):
            result = analyzer.analyze()

        assert result.success is False


class TestAzureIAMAnalyzerPrerequisites:
    """Tests for AzureIAMAnalyzer.check_prerequisites()."""

    def test_sdk_not_installed(self) -> None:
        analyzer = _make_analyzer()
        with (
            patch.dict(
                "sys.modules",
                {
                    "azure.identity": None,
                    "azure.mgmt.authorization": None,
                },
            ),
            patch(
                "builtins.__import__",
                side_effect=ImportError("No module named 'azure'"),
            ),
        ):
            issues = analyzer.check_prerequisites()

        assert len(issues) == 1
        assert "pip install" in issues[0]

    def test_auth_success(self) -> None:
        analyzer = _make_analyzer(credentials=MagicMock())
        mock_client = MagicMock()
        mock_client.role_assignments.list_for_subscription.return_value = iter(
            [],
        )
        with patch(
            "azure.mgmt.authorization.AuthorizationManagementClient",
            return_value=mock_client,
        ):
            issues = analyzer.check_prerequisites()

        assert issues == []
