from unittest import mock

from moto import mock_aws

from prowler.providers.aws.services.s3.s3_service import Bucket
from tests.providers.aws.utils import (
    AWS_ACCOUNT_NUMBER,
    AWS_REGION_US_EAST_1,
    set_mocked_aws_provider,
)


class Test_s3_bucket_shadow_resource_vulnerability:
    @mock_aws
    def test_no_buckets(self):
        aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
        aws_provider.identity.identity_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"

        s3_client = mock.MagicMock()
        s3_client.buckets = {}
        s3_client.provider = aws_provider
        s3_client._head_bucket = mock.MagicMock(return_value=False)

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=aws_provider,
            ),
            mock.patch(
                "prowler.providers.aws.services.s3.s3_bucket_shadow_resource_vulnerability.s3_bucket_shadow_resource_vulnerability.s3_client",
                new=s3_client,
            ),
        ):
            from prowler.providers.aws.services.s3.s3_bucket_shadow_resource_vulnerability.s3_bucket_shadow_resource_vulnerability import (
                s3_bucket_shadow_resource_vulnerability,
            )

            check = s3_bucket_shadow_resource_vulnerability()
            result = check.execute()

            assert len(result) == 0

    @mock_aws
    def test_bucket_owned_by_account(self):
        aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
        aws_provider.identity.identity_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"

        bucket_name = f"sagemaker-{AWS_REGION_US_EAST_1}-{AWS_ACCOUNT_NUMBER}"

        s3_client = mock.MagicMock()
        s3_client.audited_canonical_id = AWS_ACCOUNT_NUMBER
        s3_client.audited_partition = "aws"
        s3_client.buckets = {
            bucket_name: Bucket(
                name=bucket_name,
                arn=f"arn:aws:s3:::{bucket_name}",
                region=AWS_REGION_US_EAST_1,
                owner_id=AWS_ACCOUNT_NUMBER,
                tags=[{"Key": "Environment", "Value": "test"}],
            )
        }
        s3_client.provider = aws_provider
        s3_client._head_bucket = mock.MagicMock(return_value=False)

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=aws_provider,
            ),
            mock.patch(
                "prowler.providers.aws.services.s3.s3_bucket_shadow_resource_vulnerability.s3_bucket_shadow_resource_vulnerability.s3_client",
                new=s3_client,
            ),
        ):
            from prowler.providers.aws.services.s3.s3_bucket_shadow_resource_vulnerability.s3_bucket_shadow_resource_vulnerability import (
                s3_bucket_shadow_resource_vulnerability,
            )

            check = s3_bucket_shadow_resource_vulnerability()
            result = check.execute()

            assert len(result) == 1
            report = result[0]

            # Test all report attributes
            assert report.status == "PASS"
            assert report.region == AWS_REGION_US_EAST_1
            assert report.resource_id == bucket_name
            assert report.resource_arn == f"arn:aws:s3:::{bucket_name}"
            assert report.resource_tags == [{"Key": "Environment", "Value": "test"}]
            assert "is correctly owned by the audited account" in report.status_extended
            assert "SageMaker" in report.status_extended

    @mock_aws
    def test_bucket_not_predictable(self):
        # A bucket whose name does not match any predictable service pattern
        # is not a shadow resource and must not produce any finding.
        aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
        aws_provider.identity.identity_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"

        bucket_name = "my-non-predictable-bucket"

        s3_client = mock.MagicMock()
        s3_client.audited_canonical_id = AWS_ACCOUNT_NUMBER
        s3_client.audited_partition = "aws"
        s3_client.buckets = {
            bucket_name: Bucket(
                name=bucket_name,
                arn=f"arn:aws:s3:::{bucket_name}",
                region=AWS_REGION_US_EAST_1,
                owner_id=AWS_ACCOUNT_NUMBER,
                tags=[{"Key": "Project", "Value": "test-project"}],
            )
        }
        s3_client.provider = aws_provider
        s3_client._head_bucket = mock.MagicMock(return_value=False)

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=aws_provider,
            ),
            mock.patch(
                "prowler.providers.aws.services.s3.s3_bucket_shadow_resource_vulnerability.s3_bucket_shadow_resource_vulnerability.s3_client",
                new=s3_client,
            ),
        ):
            from prowler.providers.aws.services.s3.s3_bucket_shadow_resource_vulnerability.s3_bucket_shadow_resource_vulnerability import (
                s3_bucket_shadow_resource_vulnerability,
            )

            check = s3_bucket_shadow_resource_vulnerability()
            result = check.execute()

            assert len(result) == 0

    @mock_aws
    def test_only_predictable_bucket_reported_among_many(self):
        # With a mix of buckets, only the one matching a predictable pattern
        # must produce a finding; the rest must be silent.
        aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
        aws_provider.identity.identity_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"

        predictable_bucket = f"sagemaker-{AWS_REGION_US_EAST_1}-{AWS_ACCOUNT_NUMBER}"
        plain_buckets = [
            "config-bucket-data",
            "my-app-data-bucket",
            "guardduty-findings-store",
        ]

        s3_client = mock.MagicMock()
        s3_client.audited_canonical_id = AWS_ACCOUNT_NUMBER
        s3_client.audited_partition = "aws"
        s3_client.buckets = {
            name: Bucket(
                name=name,
                arn=f"arn:aws:s3:::{name}",
                region=AWS_REGION_US_EAST_1,
                owner_id=AWS_ACCOUNT_NUMBER,
                tags=[],
            )
            for name in [predictable_bucket, *plain_buckets]
        }
        s3_client.provider = aws_provider
        s3_client._head_bucket = mock.MagicMock(return_value=False)

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=aws_provider,
            ),
            mock.patch(
                "prowler.providers.aws.services.s3.s3_bucket_shadow_resource_vulnerability.s3_bucket_shadow_resource_vulnerability.s3_client",
                new=s3_client,
            ),
        ):
            from prowler.providers.aws.services.s3.s3_bucket_shadow_resource_vulnerability.s3_bucket_shadow_resource_vulnerability import (
                s3_bucket_shadow_resource_vulnerability,
            )

            check = s3_bucket_shadow_resource_vulnerability()
            result = check.execute()

            assert len(result) == 1
            report = result[0]
            assert report.status == "PASS"
            assert report.resource_id == predictable_bucket
            assert "SageMaker" in report.status_extended
            assert "is correctly owned by the audited account" in report.status_extended

    @mock_aws
    def test_shadow_resource_in_other_account(self):
        # Mock S3 client with no buckets in current account
        s3_client = mock.MagicMock()
        s3_client.buckets = {}
        s3_client.audited_canonical_id = AWS_ACCOUNT_NUMBER
        s3_client.audited_identity_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"
        s3_client.audited_partition = "aws"

        # Mock regional clients - this is what the check uses to determine regions to test
        s3_client.regional_clients = {
            "us-east-1": mock.MagicMock(),
            "us-west-2": mock.MagicMock(),
            "eu-west-1": mock.MagicMock(),
        }

        # Define the shadow resources we want to simulate
        shadow_resources = [
            f"aws-glue-assets-{AWS_ACCOUNT_NUMBER}-us-west-2",
            f"sagemaker-us-east-1-{AWS_ACCOUNT_NUMBER}",
            f"aws-emr-studio-{AWS_ACCOUNT_NUMBER}-eu-west-1",
        ]

        # Mock the _head_bucket method to simulate finding shadow resources
        def mock_head_bucket(bucket_name):
            return bucket_name in shadow_resources

        s3_client._head_bucket = mock_head_bucket

        # Mock provider with multiple regions to test
        aws_provider = set_mocked_aws_provider(["us-east-1", "us-west-2", "eu-west-1"])
        aws_provider.identity.identity_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"
        aws_provider.identity.account = AWS_ACCOUNT_NUMBER
        s3_client.provider = aws_provider

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=aws_provider,
            ),
            mock.patch(
                "prowler.providers.aws.services.s3.s3_bucket_shadow_resource_vulnerability.s3_bucket_shadow_resource_vulnerability.s3_client",
                new=s3_client,
            ),
        ):
            from prowler.providers.aws.services.s3.s3_bucket_shadow_resource_vulnerability.s3_bucket_shadow_resource_vulnerability import (
                s3_bucket_shadow_resource_vulnerability,
            )

            check = s3_bucket_shadow_resource_vulnerability()
            result = check.execute()

            # Should find shadow resources
            assert len(result) >= 3

            # Check if we found all expected shadow resources
            found_services = set()
            for finding in result:
                if (
                    finding.status == "FAIL"
                    and "shadow resource" in finding.status_extended
                ):
                    # Test all report attributes for cross-account findings
                    assert finding.status == "FAIL"
                    assert finding.resource_id in shadow_resources
                    assert finding.resource_arn == f"arn:aws:s3:::{finding.resource_id}"
                    assert finding.resource_tags == []

                    # Determine service from resource_id and test exact status_extended
                    if "aws-glue-assets" in finding.resource_id:
                        service = "Glue"
                        expected_status = f"S3 bucket {finding.resource_id} for service {service} is a known shadow resource that exists and is owned by another account."
                        assert finding.status_extended == expected_status
                        found_services.add("Glue")
                        assert finding.region == "us-west-2"
                    elif "sagemaker" in finding.resource_id:
                        service = "SageMaker"
                        expected_status = f"S3 bucket {finding.resource_id} for service {service} is a known shadow resource that exists and is owned by another account."
                        assert finding.status_extended == expected_status
                        found_services.add("SageMaker")
                        assert finding.region == "us-east-1"
                    elif "aws-emr-studio" in finding.resource_id:
                        service = "EMR"
                        expected_status = f"S3 bucket {finding.resource_id} for service {service} is a known shadow resource that exists and is owned by another account."
                        assert finding.status_extended == expected_status
                        found_services.add("EMR")
                        assert finding.region == "eu-west-1"

            # Verify we found all expected services
            expected_services = {"Glue", "SageMaker", "EMR"}
            assert found_services == expected_services
