import io
import os
import tempfile
import zipfile
from re import search
from unittest.mock import patch

import mock
from boto3 import client, resource, session
from moto import mock_iam, mock_lambda, mock_s3
from moto.core import DEFAULT_ACCOUNT_ID

from prowler.providers.aws.lib.audit_info.models import AWS_Audit_Info
from prowler.providers.aws.services.awslambda.awslambda_service import AuthType, Lambda
from prowler.providers.common.models import Audit_Metadata

# Mock Test Region
AWS_REGION = "eu-west-1"
AWS_REGION_NORTH_VIRGINIA = "us-east-1"


def create_zip_file(code: str = "") -> io.BytesIO:
    zip_output = io.BytesIO()
    zip_file = zipfile.ZipFile(zip_output, "w", zipfile.ZIP_DEFLATED)
    if not code:
        zip_file.writestr(
            "lambda_function.py",
            """
            def lambda_handler(event, context):
                print("custom log event")
                return event
            """,
        )
    else:
        zip_file.writestr("lambda_function.py", code)
    zip_file.close()
    zip_output.seek(0)
    return zip_output


def mock_request_get(_):
    """Mock requests.get() to get the Lambda Code in Zip Format"""
    mock_resp = mock.MagicMock
    mock_resp.status_code = 200
    mock_resp.content = create_zip_file().read()
    return mock_resp


# Mock generate_regional_clients()
def mock_generate_regional_clients(service, audit_info):
    regional_client_eu_west_1 = audit_info.audit_session.client(
        service, region_name=AWS_REGION
    )
    regional_client_us_east_1 = audit_info.audit_session.client(
        service, region_name=AWS_REGION_NORTH_VIRGINIA
    )
    regional_client_eu_west_1.region = AWS_REGION
    regional_client_us_east_1.region = AWS_REGION_NORTH_VIRGINIA
    return {
        AWS_REGION: regional_client_eu_west_1,
        AWS_REGION_NORTH_VIRGINIA: regional_client_us_east_1,
    }


@patch(
    "prowler.providers.aws.services.awslambda.awslambda_service.generate_regional_clients",
    new=mock_generate_regional_clients,
)
class Test_Lambda_Service:
    def set_mocked_audit_info(self):
        audit_info = AWS_Audit_Info(
            session_config=None,
            original_session=None,
            audit_session=session.Session(
                profile_name=None,
                botocore_session=None,
            ),
            audited_account=DEFAULT_ACCOUNT_ID,
            audited_user_id=None,
            audited_partition="aws",
            audited_identity_arn=None,
            profile=None,
            profile_region=None,
            credentials=None,
            assumed_role_info=None,
            audited_regions=None,
            organizations_metadata=None,
            audit_resources=None,
            audit_metadata=Audit_Metadata(
                services_scanned=0,
                # We need to set this check to call __list_functions__
                expected_checks=["awslambda_function_no_secrets_in_code"],
                completed_checks=0,
                audit_progress=0,
            ),
        )
        return audit_info

    # Test Lambda Client
    def test__get_client__(self):
        awslambda = Lambda(self.set_mocked_audit_info())
        assert awslambda.regional_clients[AWS_REGION].__class__.__name__ == "Lambda"

    # Test Lambda Session
    def test__get_session__(self):
        awslambda = Lambda(self.set_mocked_audit_info())
        assert awslambda.session.__class__.__name__ == "Session"

    # Test Lambda Service
    def test__get_service__(self):
        awslambda = Lambda(self.set_mocked_audit_info())
        assert awslambda.service == "lambda"

    @mock_lambda
    @mock_iam
    @mock_s3
    def test__list_functions__(self):
        # Create IAM Lambda Role
        iam_client = client("iam", region_name=AWS_REGION)
        iam_role = iam_client.create_role(
            RoleName="test-lambda-role",
            AssumeRolePolicyDocument="test-policy",
            Path="/",
        )["Role"]["Arn"]
        # Create S3 Bucket
        s3_client = resource("s3", region_name=AWS_REGION)
        s3_client.create_bucket(
            Bucket="test-bucket",
            CreateBucketConfiguration={"LocationConstraint": AWS_REGION},
        )
        # Create Test Lambda 1
        lambda_client = client("lambda", region_name=AWS_REGION)
        lambda_name = "test-lambda"
        resp = lambda_client.create_function(
            FunctionName=lambda_name,
            Runtime="python3.7",
            Role=iam_role,
            Handler="lambda_function.lambda_handler",
            Code={"ZipFile": create_zip_file().read()},
            Description="test lambda function",
            Timeout=3,
            MemorySize=128,
            PackageType="ZIP",
            Publish=True,
            VpcConfig={
                "SecurityGroupIds": ["sg-123abc"],
                "SubnetIds": ["subnet-123abc"],
            },
            Environment={"Variables": {"db-password": "test-password"}},
            Tags={"test": "test"},
        )
        lambda_arn_1 = resp["FunctionArn"]
        # Update Lambda Policy
        lambda_policy = {
            "Version": "2012-10-17",
            "Id": "default",
            "Statement": [
                {
                    "Action": "lambda:GetFunction",
                    "Principal": "*",
                    "Effect": "Allow",
                    "Resource": f"arn:aws:lambda:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:function:{lambda_name}",
                    "Sid": "test",
                }
            ],
        }
        _ = lambda_client.add_permission(
            FunctionName=lambda_name,
            StatementId="test",
            Action="lambda:GetFunction",
            Principal="*",
        )
        # Create Function URL Config
        _ = lambda_client.create_function_url_config(
            FunctionName=lambda_name,
            AuthType=AuthType.AWS_IAM.value,
            Cors={
                "AllowCredentials": True,
                "AllowHeaders": [
                    "string",
                ],
                "AllowMethods": [
                    "string",
                ],
                "AllowOrigins": [
                    "*",
                ],
                "ExposeHeaders": [
                    "string",
                ],
                "MaxAge": 123,
            },
        )

        # Create Test Lambda 2 (with the same attributes but different region)
        lambda_client_2 = client("lambda", region_name=AWS_REGION_NORTH_VIRGINIA)
        lambda_name = "test-lambda"
        resp_2 = lambda_client_2.create_function(
            FunctionName=lambda_name,
            Runtime="python3.7",
            Role=iam_role,
            Handler="lambda_function.lambda_handler",
            Code={"ZipFile": create_zip_file().read()},
            Description="test lambda function",
            Timeout=3,
            MemorySize=128,
            PackageType="ZIP",
            Publish=True,
            VpcConfig={
                "SecurityGroupIds": ["sg-123abc"],
                "SubnetIds": ["subnet-123abc"],
            },
            Environment={"Variables": {"db-password": "test-password"}},
            Tags={"test": "test"},
        )
        lambda_arn_2 = resp_2["FunctionArn"]

        with mock.patch(
            "prowler.providers.aws.services.awslambda.awslambda_service.requests.get",
            new=mock_request_get,
        ):
            awslambda = Lambda(self.set_mocked_audit_info())
            assert awslambda.functions
            assert len(awslambda.functions) == 2
            # Lambda 1
            assert awslambda.functions[lambda_arn_1].name == lambda_name
            assert awslambda.functions[lambda_arn_1].arn == lambda_arn_1
            assert awslambda.functions[lambda_arn_1].runtime == "python3.7"
            assert awslambda.functions[lambda_arn_1].environment == {
                "db-password": "test-password"
            }
            assert awslambda.functions[lambda_arn_1].region == AWS_REGION
            assert awslambda.functions[lambda_arn_1].policy == lambda_policy

            assert awslambda.functions[lambda_arn_1].code
            assert search(
                f"s3://awslambda-{AWS_REGION}-tasks.s3-{AWS_REGION}.amazonaws.com",
                awslambda.functions[lambda_arn_1].code.location,
            )

            assert awslambda.functions[lambda_arn_1].url_config
            assert (
                awslambda.functions[lambda_arn_1].url_config.auth_type
                == AuthType.AWS_IAM
            )
            assert search(
                "lambda-url.eu-west-1.on.aws",
                awslambda.functions[lambda_arn_1].url_config.url,
            )

            assert awslambda.functions[lambda_arn_1].url_config.cors_config
            assert awslambda.functions[
                lambda_arn_1
            ].url_config.cors_config.allow_origins == ["*"]

            assert awslambda.functions[lambda_arn_1].tags == [{"test": "test"}]

            # Pending ZipFile tests
            with tempfile.TemporaryDirectory() as tmp_dir_name:
                awslambda.functions[lambda_arn_1].code.code_zip.extractall(tmp_dir_name)
                files_in_zip = next(os.walk(tmp_dir_name))[2]
                assert len(files_in_zip) == 1
                assert files_in_zip[0] == "lambda_function.py"
                with open(f"{tmp_dir_name}/{files_in_zip[0]}", "r") as lambda_code_file:
                    _ = lambda_code_file
                    # assert (
                    #     lambda_code_file.read()
                    #     == """
                    # def lambda_handler(event, context):
                    # print("custom log event")
                    # return event
                    # """
                    # )

            # Lambda 2
            assert awslambda.functions[lambda_arn_2].name == lambda_name
            assert awslambda.functions[lambda_arn_2].arn == lambda_arn_2
            assert awslambda.functions[lambda_arn_2].runtime == "python3.7"
            assert awslambda.functions[lambda_arn_2].environment == {
                "db-password": "test-password"
            }
            assert awslambda.functions[lambda_arn_2].region == AWS_REGION_NORTH_VIRGINIA
            # Emtpy policy
            assert awslambda.functions[lambda_arn_2].policy == {
                "Id": "default",
                "Statement": [],
                "Version": "2012-10-17",
            }

            assert awslambda.functions[lambda_arn_2].code
            assert search(
                f"s3://awslambda-{AWS_REGION_NORTH_VIRGINIA}-tasks.s3-{AWS_REGION_NORTH_VIRGINIA}.amazonaws.com",
                awslambda.functions[lambda_arn_2].code.location,
            )
