import os
import zipfile
from unittest import mock

from prowler.providers.aws.services.awslambda.awslambda_service import (
    Function,
    LambdaCode,
)
from tests.providers.aws.services.awslambda.awslambda_service_test import (
    create_zip_file,
)
from tests.providers.aws.utils import (
    AWS_ACCOUNT_NUMBER,
    AWS_REGION_US_EAST_1,
    set_mocked_aws_provider,
)

LAMBDA_FUNCTION_NAME = "test-lambda"
LAMBDA_FUNCTION_RUNTIME = "nodejs4.3"
LAMBDA_FUNCTION_ARN = f"arn:aws:lambda:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:function/{LAMBDA_FUNCTION_NAME}"
LAMBDA_FUNCTION_CODE_WITH_SECRETS = """
def lambda_handler(event, context):
        db_password = "Tr0ub4dor3xKq9vLmZ"
        print("custom log event")
        return event
"""
LAMBDA_FUNCTION_CODE_WITHOUT_SECRETS = """
def lambda_handler(event, context):
        print("custom log event")
        return event
"""
LAMBDA_FUNCTION_CODE_WITH_METADATA_API = """
def lambda_handler(event, context):
        metadata_api = "169.254.169.254"
        print("custom log event")
        return event
"""


def create_lambda_function() -> Function:
    return Function(
        name=LAMBDA_FUNCTION_NAME,
        security_groups=[],
        arn=LAMBDA_FUNCTION_ARN,
        region=AWS_REGION_US_EAST_1,
        runtime=LAMBDA_FUNCTION_RUNTIME,
    )


def get_lambda_code_with_secrets(code):
    return LambdaCode(
        location="",
        code_zip=zipfile.ZipFile(create_zip_file(code)),
    )


LAMBDA_DEPS_JSON_WITH_SECRET = """
{
  "runtimeTarget": { "name": ".NETCoreApp,Version=v8.0" },
  "libraries": {
    "AWSSDK.SecretsManager/3.7.0": {
      "type": "package",
      "password": "test-deps-json-password"
    }
  }
}
"""

LAMBDA_VENDOR_JS_WITH_SECRET = """
const dbPassword = "test-vendor-password";
"""


def get_lambda_code_from_files(files: dict) -> LambdaCode:
    # The check only calls code_zip.extractall(dir); mock it to drop the
    # given files into the temporary directory the check creates, so no
    # real archive needs to be built.
    code_zip = mock.MagicMock()

    def _extractall(path):
        for name, content in files.items():
            os.makedirs(os.path.dirname(f"{path}/{name}"), exist_ok=True)
            with open(f"{path}/{name}", "w") as fd:
                fd.write(content)

    code_zip.extractall.side_effect = _extractall
    return LambdaCode(location="", code_zip=code_zip)


def mock_get_function_code_with_deps_json_secret():
    yield create_lambda_function(), get_lambda_code_from_files(
        {
            "lambda_function.py": LAMBDA_FUNCTION_CODE_WITHOUT_SECRETS,
            "myapp.deps.json": LAMBDA_DEPS_JSON_WITH_SECRET,
        }
    )


def mock_get_function_code_with_nested_vendor_secret():
    yield create_lambda_function(), get_lambda_code_from_files(
        {
            "lambda_function.py": LAMBDA_FUNCTION_CODE_WITHOUT_SECRETS,
            "vendor/package.js": LAMBDA_VENDOR_JS_WITH_SECRET,
        }
    )


def mock_get_function_codewith_secrets():
    yield create_lambda_function(), get_lambda_code_with_secrets(
        LAMBDA_FUNCTION_CODE_WITH_SECRETS
    )


def mock_get_function_codewithout_secrets():
    yield create_lambda_function(), get_lambda_code_with_secrets(
        LAMBDA_FUNCTION_CODE_WITHOUT_SECRETS
    )


def mock_get_function_codewith_metadata_api():
    yield create_lambda_function(), get_lambda_code_with_secrets(
        LAMBDA_FUNCTION_CODE_WITH_METADATA_API
    )


class Test_awslambda_function_no_secrets_in_code:
    def test_no_functions(self):
        lambda_client = mock.MagicMock
        lambda_client.functions = {}

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=set_mocked_aws_provider(),
            ),
            mock.patch(
                "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.awslambda_client",
                new=lambda_client,
            ),
        ):
            # Test Check
            from prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code import (
                awslambda_function_no_secrets_in_code,
            )

            check = awslambda_function_no_secrets_in_code()
            result = check.execute()

            assert len(result) == 0

    def test_function_code_with_secrets(self):
        lambda_client = mock.MagicMock
        lambda_client.functions = {LAMBDA_FUNCTION_ARN: create_lambda_function()}
        lambda_client._get_function_code = mock_get_function_codewith_secrets
        lambda_client.audit_config = {"secrets_ignore_patterns": []}
        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=set_mocked_aws_provider(),
            ),
            mock.patch(
                "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.awslambda_client",
                new=lambda_client,
            ),
        ):
            # Test Check
            from prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code import (
                awslambda_function_no_secrets_in_code,
            )

            check = awslambda_function_no_secrets_in_code()
            result = check.execute()

            assert len(result) == 1
            assert result[0].region == AWS_REGION_US_EAST_1
            assert result[0].resource_id == LAMBDA_FUNCTION_NAME
            assert result[0].resource_arn == LAMBDA_FUNCTION_ARN
            assert result[0].status == "FAIL"
            assert (
                result[0].status_extended
                == f"Potential secret found in Lambda function {LAMBDA_FUNCTION_NAME} code -> lambda_function.py: Generic Password on line 3."
            )
            assert result[0].resource_tags == []

    def test_function_code_without_secrets(self):
        lambda_client = mock.MagicMock
        lambda_client.functions = {LAMBDA_FUNCTION_ARN: create_lambda_function()}

        lambda_client._get_function_code = mock_get_function_codewithout_secrets
        lambda_client.audit_config = {"secrets_ignore_patterns": []}

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=set_mocked_aws_provider(),
            ),
            mock.patch(
                "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.awslambda_client",
                new=lambda_client,
            ),
        ):
            # Test Check
            from prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code import (
                awslambda_function_no_secrets_in_code,
            )

            check = awslambda_function_no_secrets_in_code()
            result = check.execute()

            assert len(result) == 1
            assert result[0].region == AWS_REGION_US_EAST_1
            assert result[0].resource_id == LAMBDA_FUNCTION_NAME
            assert result[0].resource_arn == LAMBDA_FUNCTION_ARN
            assert result[0].status == "PASS"
            assert (
                result[0].status_extended
                == f"No secrets found in Lambda function {LAMBDA_FUNCTION_NAME} code."
            )
            assert result[0].resource_tags == []

    def test_function_code_with_metadata_api(self):
        lambda_client = mock.MagicMock
        lambda_client.functions = {LAMBDA_FUNCTION_ARN: create_lambda_function()}

        lambda_client._get_function_code = mock_get_function_codewith_metadata_api
        lambda_client.audit_config = {"secrets_ignore_patterns": []}

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=set_mocked_aws_provider(),
            ),
            mock.patch(
                "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.awslambda_client",
                new=lambda_client,
            ),
        ):
            # Test Check
            from prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code import (
                awslambda_function_no_secrets_in_code,
            )

            check = awslambda_function_no_secrets_in_code()
            result = check.execute()

            assert len(result) == 1
            assert result[0].region == AWS_REGION_US_EAST_1
            assert result[0].resource_id == LAMBDA_FUNCTION_NAME
            assert result[0].resource_arn == LAMBDA_FUNCTION_ARN
            assert result[0].status == "PASS"
            assert (
                result[0].status_extended
                == f"No secrets found in Lambda function {LAMBDA_FUNCTION_NAME} code."
            )
            assert result[0].resource_tags == []

    def test_function_code_deps_json_secret_not_ignored(self):
        lambda_client = mock.MagicMock
        lambda_client.functions = {LAMBDA_FUNCTION_ARN: create_lambda_function()}
        lambda_client._get_function_code = mock_get_function_code_with_deps_json_secret
        lambda_client.audit_config = {
            "secrets_ignore_patterns": [],
            "secrets_ignore_files": None,
        }

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=set_mocked_aws_provider(),
            ),
            mock.patch(
                "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.awslambda_client",
                new=lambda_client,
            ),
        ):
            from prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code import (
                awslambda_function_no_secrets_in_code,
            )

            check = awslambda_function_no_secrets_in_code()
            result = check.execute()

            assert len(result) == 1
            assert result[0].status == "FAIL"
            assert "myapp.deps.json" in result[0].status_extended

    def test_function_code_nested_vendor_secret_not_ignored(self):
        lambda_client = mock.MagicMock
        lambda_client.functions = {LAMBDA_FUNCTION_ARN: create_lambda_function()}
        lambda_client._get_function_code = (
            mock_get_function_code_with_nested_vendor_secret
        )
        lambda_client.audit_config = {"secrets_ignore_patterns": []}

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=set_mocked_aws_provider(),
            ),
            mock.patch(
                "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.awslambda_client",
                new=lambda_client,
            ),
        ):
            from prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code import (
                awslambda_function_no_secrets_in_code,
            )

            check = awslambda_function_no_secrets_in_code()
            result = check.execute()

            assert len(result) == 1
            assert result[0].status == "FAIL"
            assert "vendor/package.js" in result[0].status_extended

    def test_function_code_nested_vendor_secret_ignored_by_file_pattern(self):
        lambda_client = mock.MagicMock
        lambda_client.functions = {LAMBDA_FUNCTION_ARN: create_lambda_function()}
        lambda_client._get_function_code = (
            mock_get_function_code_with_nested_vendor_secret
        )
        lambda_client.audit_config = {
            "secrets_ignore_patterns": [],
            "secrets_ignore_files": ["vendor/*.js"],
        }

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=set_mocked_aws_provider(),
            ),
            mock.patch(
                "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.awslambda_client",
                new=lambda_client,
            ),
        ):
            from prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code import (
                awslambda_function_no_secrets_in_code,
            )

            check = awslambda_function_no_secrets_in_code()
            result = check.execute()

            assert len(result) == 1
            assert result[0].status == "PASS"

    def test_function_code_deps_json_secret_ignored_by_file_pattern(self):
        lambda_client = mock.MagicMock
        lambda_client.functions = {LAMBDA_FUNCTION_ARN: create_lambda_function()}
        lambda_client._get_function_code = mock_get_function_code_with_deps_json_secret
        lambda_client.audit_config = {
            "secrets_ignore_patterns": [],
            "secrets_ignore_files": ["*.deps.json"],
        }

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=set_mocked_aws_provider(),
            ),
            mock.patch(
                "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.awslambda_client",
                new=lambda_client,
            ),
        ):
            from prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code import (
                awslambda_function_no_secrets_in_code,
            )

            check = awslambda_function_no_secrets_in_code()
            result = check.execute()

            assert len(result) == 1
            assert result[0].region == AWS_REGION_US_EAST_1
            assert result[0].resource_id == LAMBDA_FUNCTION_NAME
            assert result[0].resource_arn == LAMBDA_FUNCTION_ARN
            assert result[0].status == "PASS"
            assert (
                result[0].status_extended
                == f"No secrets found in Lambda function {LAMBDA_FUNCTION_NAME} code."
            )
            assert result[0].resource_tags == []

    def test_scan_failure_reports_manual_not_pass(self):
        from prowler.lib.utils.utils import SecretsScanError

        lambda_client = mock.MagicMock
        lambda_client.functions = {LAMBDA_FUNCTION_ARN: create_lambda_function()}
        lambda_client._get_function_code = mock_get_function_codewith_secrets
        lambda_client.audit_config = {"secrets_ignore_patterns": []}

        with (
            mock.patch(
                "prowler.providers.common.provider.Provider.get_global_provider",
                return_value=set_mocked_aws_provider(),
            ),
            mock.patch(
                "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.awslambda_client",
                new=lambda_client,
            ),
            mock.patch(
                "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.detect_secrets_scan_batch",
                side_effect=SecretsScanError("Kingfisher exited with code 1"),
            ),
        ):
            from prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code import (
                awslambda_function_no_secrets_in_code,
            )

            check = awslambda_function_no_secrets_in_code()
            result = check.execute()

            assert len(result) == 1
            assert result[0].status == "MANUAL"
            assert "Could not scan" in result[0].status_extended
