"""Tests for AWSIAMAnalyzer using unittest.mock."""

from __future__ import annotations

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

from complyform.core.cloud_intelligence.iam_analyzer import IAMAnalysisResult
from complyform.core.cloud_intelligence.iam_aws import AWSIAMAnalyzer

# Ensure boto3 is mockable even when not installed.
_mock_boto3 = MagicMock()
if "boto3" not in sys.modules:
    sys.modules["boto3"] = _mock_boto3


def _make_analyzer(session: MagicMock | None = None) -> AWSIAMAnalyzer:
    return AWSIAMAnalyzer(
        cloud="aws",
        project_id="123456789012",
        credentials=session,
    )


def _mock_session_with_analyzer() -> MagicMock:
    """Create a mock boto3 session with an UNUSED_ACCESS analyzer."""
    session = MagicMock()
    client = MagicMock()
    session.client.return_value = client
    client.list_analyzers.return_value = {
        "analyzers": [
            {
                "arn": "arn:aws:access-analyzer:us-east-1:123:analyzer/test",
                "type": "UNUSED_ACCESS",
            }
        ]
    }
    return session


class TestAWSIAMAnalyzerAnalyze:
    """Tests for AWSIAMAnalyzer.analyze()."""

    def test_no_analyzer_returns_success_false(self) -> None:
        session = MagicMock()
        client = MagicMock()
        session.client.return_value = client
        client.list_analyzers.return_value = {"analyzers": []}

        analyzer = _make_analyzer(session)
        result = analyzer.analyze()

        assert isinstance(result, IAMAnalysisResult)
        assert result.success is False
        assert "No unused access analyzer" in (result.error or "")

    def test_findings_extracted_correctly(self) -> None:
        session = _mock_session_with_analyzer()
        client = session.client.return_value
        client.list_findings_v2.return_value = {
            "findings": [
                {
                    "findingId": "f1",
                    "findingType": "UnusedIAMRole",
                    "resource": "arn:aws:iam::123:role/unused-role",
                    "resourceOwnerAccount": "123",
                    "updatedAt": datetime(2025, 1, 1, tzinfo=UTC),
                    "status": "ACTIVE",
                },
                {
                    "findingId": "f2",
                    "findingType": "UnusedPermission",
                    "resource": "arn:aws:iam::123:user/over-provisioned",
                    "resourceOwnerAccount": "123",
                    "updatedAt": datetime(2025, 1, 15, tzinfo=UTC),
                    "status": "ACTIVE",
                    "findingDetails": [
                        {
                            "unusedPermissionDetails": {
                                "serviceNamespace": "s3",
                                "actions": ["PutObject", "DeleteObject"],
                            }
                        }
                    ],
                },
            ]
        }

        analyzer = _make_analyzer(session)
        result = analyzer.analyze()

        assert result.success is True
        assert len(result.findings) == 2
        # UnusedIAMRole -> recommended_role=None (remove)
        assert result.findings[0].recommended_role is None
        # UnusedPermission -> has unused_permissions
        assert result.findings[1].recommended_role == "scoped_policy"
        assert "s3:PutObject" in result.findings[1].unused_permissions

    def test_pagination_follows_next_token(self) -> None:
        session = _mock_session_with_analyzer()
        client = session.client.return_value
        client.list_findings_v2.side_effect = [
            {
                "findings": [
                    {
                        "findingId": "f1",
                        "findingType": "UnusedIAMRole",
                        "resource": "arn:aws:iam::123:role/r1",
                        "updatedAt": "2025-01-01",
                    }
                ],
                "nextToken": "tok1",
            },
            {
                "findings": [
                    {
                        "findingId": "f2",
                        "findingType": "UnusedIAMRole",
                        "resource": "arn:aws:iam::123:role/r2",
                        "updatedAt": "2025-01-02",
                    }
                ],
            },
        ]

        analyzer = _make_analyzer(session)
        result = analyzer.analyze()

        assert result.success is True
        assert len(result.findings) == 2

    def test_access_denied_returns_success_false(self) -> None:
        session = MagicMock()
        client = MagicMock()
        session.client.return_value = client

        error = Exception("AccessDeniedException")
        error.response = {  # type: ignore[attr-defined]
            "Error": {
                "Code": "AccessDeniedException",
                "Message": "denied",
            }
        }
        client.list_analyzers.side_effect = error

        analyzer = _make_analyzer(session)
        result = analyzer.analyze()

        assert result.success is False

    def test_throttling_retries(self) -> None:
        session = _mock_session_with_analyzer()
        client = session.client.return_value

        error = Exception("ThrottlingException")
        error.response = {  # type: ignore[attr-defined]
            "Error": {
                "Code": "ThrottlingException",
                "Message": "throttled",
            }
        }
        client.list_findings_v2.side_effect = error

        analyzer = _make_analyzer(session)
        with patch(
            "complyform.core.cloud_intelligence.iam_aws.time.sleep",
        ):
            result = analyzer.analyze()

        assert isinstance(result, IAMAnalysisResult)


class TestAWSIAMAnalyzerPrerequisites:
    """Tests for AWSIAMAnalyzer.check_prerequisites()."""

    def test_no_analyzer_found(self) -> None:
        session = MagicMock()
        client = MagicMock()
        session.client.return_value = client
        client.list_analyzers.return_value = {"analyzers": []}

        analyzer = _make_analyzer(session)
        issues = analyzer.check_prerequisites()

        assert len(issues) == 1
        assert "create-analyzer" in issues[0]

    def test_analyzer_exists(self) -> None:
        session = _mock_session_with_analyzer()
        analyzer = _make_analyzer(session)
        issues = analyzer.check_prerequisites()
        assert issues == []

    def test_access_denied(self) -> None:
        session = MagicMock()
        client = MagicMock()
        session.client.return_value = client

        error = Exception("AccessDeniedException")
        error.response = {  # type: ignore[attr-defined]
            "Error": {
                "Code": "AccessDeniedException",
                "Message": "denied",
            }
        }
        client.list_analyzers.side_effect = error

        analyzer = _make_analyzer(session)
        issues = analyzer.check_prerequisites()

        assert len(issues) == 1
        assert "Missing permission" in issues[0]
