import json
import os
from functools import wraps
from time import sleep
from uuid import uuid4

import boto3
from botocore.exceptions import ClientError

from moto import mock_aws


def lambda_aws_verified(func):
    """
    Function that is verified to work against AWS.
    Can be run against AWS at any time by setting:
      MOTO_TEST_ALLOW_AWS_REQUEST=true

    If this environment variable is not set, the function runs in a `mock_aws` context.

    This decorator will:
      - Create an IAM-role that can be used by AWSLambda functions table
      - Run the test
      - Delete the role
    """

    @wraps(func)
    def pagination_wrapper(**kwargs):
        role_name = "moto_test_role_" + str(uuid4())[0:6]

        allow_aws_request = (
            os.environ.get("MOTO_TEST_ALLOW_AWS_REQUEST", "false").lower() == "true"
        )

        if allow_aws_request:
            return create_role_and_test(kwargs, role_name)
        else:
            with mock_aws():
                return create_role_and_test(kwargs, role_name)

    def create_role_and_test(kwargs, role_name):
        from .test_lambda import _lambda_region

        policy_doc = {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "LambdaAssumeRole",
                    "Effect": "Allow",
                    "Principal": {"Service": "lambda.amazonaws.com"},
                    "Action": "sts:AssumeRole",
                }
            ],
        }
        iam = boto3.client("iam", region_name=_lambda_region)
        iam_role_arn = iam.create_role(
            RoleName=role_name,
            AssumeRolePolicyDocument=json.dumps(policy_doc),
            Path="/",
        )["Role"]["Arn"]

        try:
            # Role is not immediately available
            check_iam_role_is_ready_to_use(iam_role_arn, role_name, _lambda_region)

            kwargs["iam_role_arn"] = iam_role_arn
            resp = func(**kwargs)
        finally:
            for policy in iam.list_attached_role_policies(RoleName=role_name)[
                "AttachedPolicies"
            ]:
                iam.detach_role_policy(
                    RoleName=role_name,
                    PolicyArn=policy["PolicyArn"],
                )
            iam.delete_role(RoleName=role_name)

        return resp

    def check_iam_role_is_ready_to_use(role_arn: str, role_name: str, region: str):
        from .utilities import get_test_zip_file1

        iam = boto3.client("iam", region_name=region)

        # The waiter is normally used to wait until a resource is ready
        wait_for_role = iam.get_waiter("role_exists")
        wait_for_role.wait(RoleName=role_name)

        # HOWEVER:
        # Just because the role exists, doesn't mean it can be used by Lambda
        # The only reliable way to check if our role is ready:
        #   - try to create a function and see if it succeeds
        #
        # The alternive is to wait between 5 and 10 seconds, which is not a great solution either
        _lambda = boto3.client("lambda", region)
        for _ in range(15):
            try:
                fn_name = f"fn_for_{role_name}"
                _lambda.create_function(
                    FunctionName=fn_name,
                    Runtime="python3.11",
                    Role=role_arn,
                    Handler="lambda_function.lambda_handler",
                    Code={"ZipFile": get_test_zip_file1()},
                )
                # If it succeeded, our role is ready
                _lambda.delete_function(FunctionName=fn_name)

                return
            except ClientError:
                sleep(1)
        raise Exception(
            f"Couldn't create test Lambda FN using IAM role {role_name}, probably because it wasn't ready in time"
        )

    return pagination_wrapper
