import re

import boto3
import botocore
from mock import patch
from moto import mock_iam, mock_sts

from prowler.providers.aws.lib.arn.arn import parse_iam_credentials_arn
from prowler.providers.aws.lib.credentials.credentials import (
    create_sts_session,
    validate_aws_credentials,
)

AWS_ACCOUNT_NUMBER = "123456789012"


# Mocking GetCallerIdentity for China and GovCloud
make_api_call = botocore.client.BaseClient._make_api_call


def mock_get_caller_identity_china(self, operation_name, kwarg):
    if operation_name == "GetCallerIdentity":
        return {
            "UserId": "XXXXXXXXXXXXXXXXXXXXX",
            "Account": AWS_ACCOUNT_NUMBER,
            "Arn": f"arn:aws-cn:iam::{AWS_ACCOUNT_NUMBER}:user/test-user",
        }

    return make_api_call(self, operation_name, kwarg)


def mock_get_caller_identity_gov_cloud(self, operation_name, kwarg):
    if operation_name == "GetCallerIdentity":
        return {
            "UserId": "XXXXXXXXXXXXXXXXXXXXX",
            "Account": AWS_ACCOUNT_NUMBER,
            "Arn": f"arn:aws-us-gov:iam::{AWS_ACCOUNT_NUMBER}:user/test-user",
        }

    return make_api_call(self, operation_name, kwarg)


class Test_AWS_Credentials:
    @mock_sts
    @mock_iam
    def test_validate_credentials_commercial_partition_with_regions(self):
        # AWS Region for AWS COMMERCIAL
        aws_region = "eu-west-1"
        aws_partition = "aws"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]

        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )

        get_caller_identity = validate_aws_credentials(session, [aws_region])

        assert get_caller_identity["region"] == aws_region

        caller_identity_arn = parse_iam_credentials_arn(get_caller_identity["Arn"])

        assert caller_identity_arn.partition == aws_partition
        assert caller_identity_arn.region is None
        assert caller_identity_arn.resource == "test-user"
        assert caller_identity_arn.resource_type == "user"
        assert re.match("[0-9a-zA-Z]{20}", get_caller_identity["UserId"])
        assert get_caller_identity["Account"] == AWS_ACCOUNT_NUMBER

    @mock_sts
    @mock_iam
    def test_validate_credentials_commercial_partition_with_regions_none_and_profile_region_so_profile_region(
        self,
    ):
        # AWS Region for AWS COMMERCIAL
        aws_region = "eu-west-1"
        aws_partition = "aws"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]

        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )

        get_caller_identity = validate_aws_credentials(session, None)

        assert get_caller_identity["region"] == aws_region

        caller_identity_arn = parse_iam_credentials_arn(get_caller_identity["Arn"])

        assert caller_identity_arn.partition == aws_partition
        assert caller_identity_arn.region is None
        assert caller_identity_arn.resource == "test-user"
        assert caller_identity_arn.resource_type == "user"
        assert re.match("[0-9a-zA-Z]{20}", get_caller_identity["UserId"])
        assert get_caller_identity["Account"] == AWS_ACCOUNT_NUMBER

    @mock_sts
    @mock_iam
    def test_validate_credentials_commercial_partition_with_0_regions_and_profile_region_so_profile_region(
        self,
    ):
        # AWS Region for AWS COMMERCIAL
        aws_region = "eu-west-1"
        aws_partition = "aws"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]

        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )

        get_caller_identity = validate_aws_credentials(session, [])

        assert get_caller_identity["region"] == aws_region

        caller_identity_arn = parse_iam_credentials_arn(get_caller_identity["Arn"])

        assert caller_identity_arn.partition == aws_partition
        assert caller_identity_arn.region is None
        assert caller_identity_arn.resource == "test-user"
        assert caller_identity_arn.resource_type == "user"
        assert re.match("[0-9a-zA-Z]{20}", get_caller_identity["UserId"])
        assert get_caller_identity["Account"] == AWS_ACCOUNT_NUMBER

    @mock_sts
    @mock_iam
    def test_validate_credentials_commercial_partition_without_regions_and_profile_region_so_us_east_1(
        self,
    ):
        # AWS Region for AWS COMMERCIAL
        aws_region = "eu-west-1"
        aws_partition = "aws"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]

        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=None,
        )

        get_caller_identity = validate_aws_credentials(session, [])

        assert get_caller_identity["region"] == "us-east-1"

        caller_identity_arn = parse_iam_credentials_arn(get_caller_identity["Arn"])

        assert caller_identity_arn.partition == aws_partition
        assert caller_identity_arn.region is None
        assert caller_identity_arn.resource == "test-user"
        assert caller_identity_arn.resource_type == "user"
        assert re.match("[0-9a-zA-Z]{20}", get_caller_identity["UserId"])
        assert get_caller_identity["Account"] == AWS_ACCOUNT_NUMBER

    @mock_sts
    @mock_iam
    def test_validate_credentials_commercial_partition_with_regions_none_and_profile_region_but_sts_endpoint_region(
        self,
    ):
        # AWS Region for AWS COMMERCIAL
        aws_region = "eu-west-1"
        sts_endpoint_region = aws_region
        aws_partition = "aws"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]

        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )

        get_caller_identity = validate_aws_credentials(
            session, None, sts_endpoint_region
        )

        assert get_caller_identity["region"] == aws_region

        caller_identity_arn = parse_iam_credentials_arn(get_caller_identity["Arn"])

        assert caller_identity_arn.partition == aws_partition
        assert caller_identity_arn.region is None
        assert caller_identity_arn.resource == "test-user"
        assert caller_identity_arn.resource_type == "user"
        assert re.match("[0-9a-zA-Z]{20}", get_caller_identity["UserId"])
        assert get_caller_identity["Account"] == AWS_ACCOUNT_NUMBER

    @mock_sts
    @mock_iam
    def test_validate_credentials_china_partition_without_regions_and_profile_region_so_us_east_1(
        self,
    ):
        # AWS Region for AWS COMMERCIAL
        aws_region = "eu-west-1"
        aws_partition = "aws"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]

        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=None,
        )

        get_caller_identity = validate_aws_credentials(session, [])

        assert get_caller_identity["region"] == "us-east-1"

        caller_identity_arn = parse_iam_credentials_arn(get_caller_identity["Arn"])

        assert caller_identity_arn.partition == aws_partition
        assert caller_identity_arn.region is None
        assert caller_identity_arn.resource == "test-user"
        assert caller_identity_arn.resource_type == "user"
        assert re.match("[0-9a-zA-Z]{20}", get_caller_identity["UserId"])
        assert get_caller_identity["Account"] == AWS_ACCOUNT_NUMBER

    @mock_sts
    @mock_iam
    @patch(
        "botocore.client.BaseClient._make_api_call", new=mock_get_caller_identity_china
    )
    def test_validate_credentials_china_partition(self):
        # AWS Region for AWS CHINA
        aws_region = "cn-north-1"
        aws_partition = "aws-cn"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]

        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )

        get_caller_identity = validate_aws_credentials(session, [aws_region])

        # To use GovCloud or China it is either required:
        # - Set the AWS profile region with a valid partition region
        # - Use the -f/--region with a valid partition region
        assert get_caller_identity["region"] == aws_region

        caller_identity_arn = parse_iam_credentials_arn(get_caller_identity["Arn"])

        assert caller_identity_arn.partition == aws_partition
        assert caller_identity_arn.region is None
        assert caller_identity_arn.resource == "test-user"
        assert caller_identity_arn.resource_type == "user"
        assert re.match("[0-9a-zA-Z]{20}", get_caller_identity["UserId"])
        assert get_caller_identity["Account"] == AWS_ACCOUNT_NUMBER

    @mock_sts
    @mock_iam
    @patch(
        "botocore.client.BaseClient._make_api_call", new=mock_get_caller_identity_china
    )
    def test_validate_credentials_china_partition_without_regions_but_sts_endpoint_region(
        self,
    ):
        # AWS Region for AWS CHINA
        aws_region = "cn-north-1"
        sts_endpoint_region = aws_region
        aws_partition = "aws-cn"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]

        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )

        get_caller_identity = validate_aws_credentials(
            session, None, sts_endpoint_region
        )

        # To use GovCloud or China it is either required:
        # - Set the AWS profile region with a valid partition region
        # - Use the -f/--region with a valid partition region
        assert get_caller_identity["region"] == aws_region

        caller_identity_arn = parse_iam_credentials_arn(get_caller_identity["Arn"])

        assert caller_identity_arn.partition == aws_partition
        assert caller_identity_arn.region is None
        assert caller_identity_arn.resource == "test-user"
        assert caller_identity_arn.resource_type == "user"
        assert re.match("[0-9a-zA-Z]{20}", get_caller_identity["UserId"])
        assert get_caller_identity["Account"] == AWS_ACCOUNT_NUMBER

    @mock_sts
    @mock_iam
    @patch(
        "botocore.client.BaseClient._make_api_call",
        new=mock_get_caller_identity_gov_cloud,
    )
    def test_validate_credentials_gov_cloud_partition(self):
        # AWS Region for US GOV CLOUD
        aws_region = "us-gov-east-1"
        aws_partition = "aws-us-gov"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]

        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )

        get_caller_identity = validate_aws_credentials(session, [aws_region])

        # To use GovCloud or China it is either required:
        # - Set the AWS profile region with a valid partition region
        # - Use the -f/--region with a valid partition region
        assert get_caller_identity["region"] == aws_region

        caller_identity_arn = parse_iam_credentials_arn(get_caller_identity["Arn"])

        assert caller_identity_arn.partition == aws_partition
        assert caller_identity_arn.region is None
        assert caller_identity_arn.resource == "test-user"
        assert caller_identity_arn.resource_type == "user"
        assert re.match("[0-9a-zA-Z]{20}", get_caller_identity["UserId"])
        assert get_caller_identity["Account"] == AWS_ACCOUNT_NUMBER

    @mock_sts
    @mock_iam
    @patch(
        "botocore.client.BaseClient._make_api_call",
        new=mock_get_caller_identity_gov_cloud,
    )
    def test_validate_credentials_gov_cloud_partition_without_regions_but_sts_endpoint_region(
        self,
    ):
        # AWS Region for US GOV CLOUD
        aws_region = "us-gov-east-1"
        sts_endpoint_region = aws_region
        aws_partition = "aws-us-gov"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]

        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )

        get_caller_identity = validate_aws_credentials(
            session, None, sts_endpoint_region
        )

        # To use GovCloud or China it is either required:
        # - Set the AWS profile region with a valid partition region
        # - Use the -f/--region with a valid partition region
        assert get_caller_identity["region"] == aws_region

        caller_identity_arn = parse_iam_credentials_arn(get_caller_identity["Arn"])

        assert caller_identity_arn.partition == aws_partition
        assert caller_identity_arn.region is None
        assert caller_identity_arn.resource == "test-user"
        assert caller_identity_arn.resource_type == "user"
        assert re.match("[0-9a-zA-Z]{20}", get_caller_identity["UserId"])
        assert get_caller_identity["Account"] == AWS_ACCOUNT_NUMBER

    @mock_iam
    @mock_sts
    def test_create_sts_session(self):
        aws_region = "eu-west-1"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]
        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )
        sts_client = create_sts_session(session, aws_region)

        assert sts_client._endpoint._endpoint_prefix == "sts"
        assert sts_client._endpoint.host == f"https://sts.{aws_region}.amazonaws.com"

    @mock_iam
    @mock_sts
    def test_create_sts_session_gov_cloud(self):
        aws_region = "us-gov-east-1"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]
        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )
        sts_client = create_sts_session(session, aws_region)

        assert sts_client._endpoint._endpoint_prefix == "sts"
        assert sts_client._endpoint.host == f"https://sts.{aws_region}.amazonaws.com"

    @mock_iam
    @mock_sts
    def test_create_sts_session_china(self):
        aws_region = "cn-north-1"
        # Create a mock IAM user
        iam_client = boto3.client("iam", region_name=aws_region)
        iam_user = iam_client.create_user(UserName="test-user")["User"]
        # Create a mock IAM access keys
        access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
            "AccessKey"
        ]
        access_key_id = access_key["AccessKeyId"]
        secret_access_key = access_key["SecretAccessKey"]
        # Create AWS session to validate
        session = boto3.session.Session(
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            region_name=aws_region,
        )
        sts_client = create_sts_session(session, aws_region)

        assert sts_client._endpoint._endpoint_prefix == "sts"
        assert sts_client._endpoint.host == f"https://sts.{aws_region}.amazonaws.com"
