from unittest.mock import MagicMock, patch

import pytest

from prowler.providers.oraclecloud.exceptions.exceptions import (
    OCIAuthenticationError,
    OCIInvalidConfigError,
)
from prowler.providers.oraclecloud.models import OCIIdentityInfo, OCIRegion, OCISession
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider


class TestSetIdentityAuthenticationErrors:
    """Tests for authentication error handling in set_identity()"""

    @pytest.fixture
    def mock_session(self):
        """Create a mock OCI session."""
        session = OCISession(
            config={
                "tenancy": "ocid1.tenancy.oc1..aaaaaaaexample",
                "user": "ocid1.user.oc1..aaaaaaaexample",
                "region": "us-ashburn-1",
                "fingerprint": "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
            },
            signer=None,
            profile="DEFAULT",
        )
        return session

    def test_authentication_error_401_raises_exception(self, mock_session):
        """Test 401 error raises OCIAuthenticationError."""
        with patch("oci.identity.IdentityClient") as mock_identity_client:
            mock_client_instance = MagicMock()
            mock_client_instance.get_tenancy.side_effect = self._create_service_error(
                401, "Authentication failed"
            )
            mock_identity_client.return_value = mock_client_instance

            with pytest.raises(OCIAuthenticationError) as exc_info:
                OraclecloudProvider.set_identity(mock_session)

            assert "OCI credential validation failed" in str(exc_info.value)

    def test_authentication_error_403_raises_exception(self, mock_session):
        """Test 403 error raises OCIAuthenticationError."""
        with patch("oci.identity.IdentityClient") as mock_identity_client:
            mock_client_instance = MagicMock()
            mock_client_instance.get_tenancy.side_effect = self._create_service_error(
                403, "Forbidden access"
            )
            mock_identity_client.return_value = mock_client_instance

            with pytest.raises(OCIAuthenticationError) as exc_info:
                OraclecloudProvider.set_identity(mock_session)

            assert "OCI credential validation failed" in str(exc_info.value)

    def test_authentication_error_404_raises_exception(self, mock_session):
        """Test 404 error raises OCIAuthenticationError."""
        with patch("oci.identity.IdentityClient") as mock_identity_client:
            mock_client_instance = MagicMock()
            mock_client_instance.get_tenancy.side_effect = self._create_service_error(
                404, "Resource not found"
            )
            mock_identity_client.return_value = mock_client_instance

            with pytest.raises(OCIAuthenticationError) as exc_info:
                OraclecloudProvider.set_identity(mock_session)

            assert "OCI credential validation failed" in str(exc_info.value)

    def test_service_error_500_raises_exception(self, mock_session):
        """Test 500 error raises OCIAuthenticationError (can't validate credentials)."""
        with patch("oci.identity.IdentityClient") as mock_identity_client:
            mock_client_instance = MagicMock()
            mock_client_instance.get_tenancy.side_effect = self._create_service_error(
                500, "Internal server error"
            )
            mock_identity_client.return_value = mock_client_instance

            with pytest.raises(OCIAuthenticationError) as exc_info:
                OraclecloudProvider.set_identity(mock_session)

            assert "OCI credential validation failed" in str(exc_info.value)

    def test_invalid_private_key_raises_exception(self, mock_session):
        """Test InvalidPrivateKey exception raises OCIAuthenticationError."""
        with patch("oci.identity.IdentityClient") as mock_identity_client:
            import oci

            mock_client_instance = MagicMock()
            mock_client_instance.get_tenancy.side_effect = (
                oci.exceptions.InvalidPrivateKey("Invalid private key")
            )
            mock_identity_client.return_value = mock_client_instance

            with pytest.raises(OCIAuthenticationError) as exc_info:
                OraclecloudProvider.set_identity(mock_session)

            assert "Invalid OCI private key format" in str(exc_info.value)

    def test_generic_exception_raises_authentication_error(self, mock_session):
        """Test generic exception raises OCIAuthenticationError."""
        with patch("oci.identity.IdentityClient") as mock_identity_client:
            mock_client_instance = MagicMock()
            mock_client_instance.get_tenancy.side_effect = Exception("Unexpected error")
            mock_identity_client.return_value = mock_client_instance

            with pytest.raises(OCIAuthenticationError) as exc_info:
                OraclecloudProvider.set_identity(mock_session)

            assert "Failed to authenticate with OCI" in str(exc_info.value)

    def test_successful_authentication(self, mock_session):
        """Test successful authentication returns identity info."""
        with patch("oci.identity.IdentityClient") as mock_identity_client:
            mock_tenancy = MagicMock()
            mock_tenancy.name = "test-tenancy"
            mock_response = MagicMock()
            mock_response.data = mock_tenancy

            mock_client_instance = MagicMock()
            mock_client_instance.get_tenancy.return_value = mock_response
            mock_identity_client.return_value = mock_client_instance

            identity = OraclecloudProvider.set_identity(mock_session)

            assert identity.tenancy_name == "test-tenancy"
            assert identity.tenancy_id == "ocid1.tenancy.oc1..aaaaaaaexample"
            assert identity.user_id == "ocid1.user.oc1..aaaaaaaexample"
            assert identity.region == "us-ashburn-1"

    @staticmethod
    def _create_service_error(status, message):
        """Helper to create an OCI ServiceError."""
        import oci

        error = oci.exceptions.ServiceError(
            status=status,
            code="TestError",
            headers={},
            message=message,
        )
        return error


class TestTestConnectionKeyValidation:
    """Tests for key_content validation in test_connection()"""

    def test_test_connection_invalid_base64_key_raises_error(self):
        """Test invalid base64 key content raises OCIInvalidConfigError."""
        with pytest.raises(OCIInvalidConfigError) as exc_info:
            OraclecloudProvider.test_connection(
                oci_config_file=None,
                profile=None,
                key_content="not-valid-base64!!!",
                user="ocid1.user.oc1..aaaaaaaexample",
                fingerprint="aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
                tenancy="ocid1.tenancy.oc1..aaaaaaaexample",
                region="us-ashburn-1",
            )

        assert "Failed to decode key_content" in str(exc_info.value)

    def test_test_connection_valid_key_content_proceeds(self):
        """Test valid base64 key content proceeds to authentication."""
        import base64

        # The SDK will validate the actual key format during authentication
        valid_key = """-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8n0sMcD/QHWCJ7yGSEtLN2T
...key content...
-----END RSA PRIVATE KEY-----"""
        encoded_key = base64.b64encode(valid_key.encode("utf-8")).decode("utf-8")

        with (
            patch("oci.config.validate_config"),
            patch("oci.identity.IdentityClient") as mock_identity_client,
        ):
            mock_tenancy = MagicMock()
            mock_tenancy.name = "test-tenancy"
            mock_response = MagicMock()
            mock_response.data = mock_tenancy

            mock_client_instance = MagicMock()
            mock_client_instance.get_tenancy.return_value = mock_response
            mock_identity_client.return_value = mock_client_instance

            connection = OraclecloudProvider.test_connection(
                oci_config_file=None,
                profile=None,
                key_content=encoded_key,
                user="ocid1.user.oc1..aaaaaaaexample",
                fingerprint="aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
                tenancy="ocid1.tenancy.oc1..aaaaaaaexample",
                region="us-ashburn-1",
                raise_on_exception=False,
            )

            assert connection.is_connected is True


class TestOraclecloudProviderInit:
    """Tests for OraclecloudProvider initialization"""

    def test_init_with_region_set_populates_provider_state(self):
        mock_session = OCISession(
            config={"region": "us-ashburn-1"}, signer=None, profile="DEFAULT"
        )
        mock_identity = OCIIdentityInfo(
            tenancy_id="ocid1.tenancy.oc1..aaaaaaaexample",
            tenancy_name="test-tenancy",
            user_id="ocid1.user.oc1..aaaaaaaexample",
            region="us-ashburn-1",
            profile="DEFAULT",
            audited_regions=set(),
            audited_compartments=[],
        )
        mock_regions = [
            OCIRegion(key="us-phoenix-1", name="us-phoenix-1", is_home_region=False),
            OCIRegion(key="us-ashburn-1", name="us-ashburn-1", is_home_region=True),
        ]
        mock_compartments = ["ocid1.compartment.oc1..aaaaaaaexample"]
        with (
            patch(
                "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.setup_session",
                return_value=mock_session,
            ) as mock_setup_session,
            patch(
                "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.set_identity",
                return_value=mock_identity,
            ),
            patch(
                "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_regions_to_audit",
                return_value=mock_regions,
            ),
            patch(
                "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_compartments_to_audit",
                return_value=mock_compartments,
            ),
            patch(
                "prowler.providers.common.provider.Provider.set_global_provider"
            ) as mock_set_global,
        ):
            provider = OraclecloudProvider(
                region={"us-ashburn-1"},
                config_content={"dummy": True},
                mutelist_content={"Accounts": {}},
            )
        assert mock_setup_session.call_args.kwargs["region"] == "us-ashburn-1"
        assert provider.session == mock_session
        assert provider.identity == mock_identity
        assert provider.regions == mock_regions
        assert provider.compartments == mock_compartments
        assert provider.home_region == "us-ashburn-1"
        mock_set_global.assert_called_once_with(provider)

    def test_home_region_uses_full_subscription_list_not_region_filter(self):
        """Home region must come from the full subscription list, not the --region filter.

        When auditing a single non-home region, the tenancy home region must still be
        resolved correctly so tenancy-level APIs (e.g. the Audit configuration) target it.
        """
        mock_session = OCISession(
            config={"region": "eu-frankfurt-1"}, signer=None, profile="DEFAULT"
        )
        mock_identity = OCIIdentityInfo(
            tenancy_id="ocid1.tenancy.oc1..aaaaaaaexample",
            tenancy_name="test-tenancy",
            user_id="ocid1.user.oc1..aaaaaaaexample",
            region="eu-frankfurt-1",
            profile="DEFAULT",
            audited_regions=set(),
            audited_compartments=[],
        )
        # The audited set is the non-home region; the full subscription list includes home
        audited_regions = [
            OCIRegion(
                key="eu-frankfurt-1", name="eu-frankfurt-1", is_home_region=False
            ),
        ]
        all_subscribed_regions = [
            OCIRegion(
                key="eu-frankfurt-1", name="eu-frankfurt-1", is_home_region=False
            ),
            OCIRegion(key="us-ashburn-1", name="us-ashburn-1", is_home_region=True),
        ]
        with (
            patch(
                "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.setup_session",
                return_value=mock_session,
            ),
            patch(
                "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.set_identity",
                return_value=mock_identity,
            ),
            patch(
                "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_regions_to_audit",
                side_effect=[audited_regions, all_subscribed_regions],
            ),
            patch(
                "prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_compartments_to_audit",
                return_value=["ocid1.compartment.oc1..aaaaaaaexample"],
            ),
            patch("prowler.providers.common.provider.Provider.set_global_provider"),
        ):
            provider = OraclecloudProvider(
                region={"eu-frankfurt-1"},
                config_content={"dummy": True},
                mutelist_content={"Accounts": {}},
            )

        assert provider.regions == audited_regions
        assert provider.home_region == "us-ashburn-1"
