import json

import boto3
import sure  # noqa # pylint: disable=unused-import
from botocore.exceptions import ClientError

import pytest

from moto import mock_iam, mock_ec2, mock_s3, mock_sts, mock_elbv2, mock_rds
from moto.core import set_initial_no_auth_action_count
from moto.core import ACCOUNT_ID
from uuid import uuid4


@mock_iam
def create_user_with_access_key(user_name="test-user"):
    client = boto3.client("iam", region_name="us-east-1")
    client.create_user(UserName=user_name)
    return client.create_access_key(UserName=user_name)["AccessKey"]


@mock_iam
def create_user_with_access_key_and_inline_policy(
    user_name, policy_document, policy_name="policy1"
):
    client = boto3.client("iam", region_name="us-east-1")
    client.create_user(UserName=user_name)
    client.put_user_policy(
        UserName=user_name,
        PolicyName=policy_name,
        PolicyDocument=json.dumps(policy_document),
    )
    return client.create_access_key(UserName=user_name)["AccessKey"]


@mock_iam
def create_user_with_access_key_and_attached_policy(
    user_name, policy_document, policy_name="policy1"
):
    client = boto3.client("iam", region_name="us-east-1")
    client.create_user(UserName=user_name)
    policy_arn = client.create_policy(
        PolicyName=policy_name, PolicyDocument=json.dumps(policy_document)
    )["Policy"]["Arn"]
    client.attach_user_policy(UserName=user_name, PolicyArn=policy_arn)
    return client.create_access_key(UserName=user_name)["AccessKey"]


@mock_iam
def create_user_with_access_key_and_multiple_policies(
    user_name,
    inline_policy_document,
    attached_policy_document,
    inline_policy_name="policy1",
    attached_policy_name="policy1",
):
    client = boto3.client("iam", region_name="us-east-1")
    client.create_user(UserName=user_name)
    policy_arn = client.create_policy(
        PolicyName=attached_policy_name,
        PolicyDocument=json.dumps(attached_policy_document),
    )["Policy"]["Arn"]
    client.attach_user_policy(UserName=user_name, PolicyArn=policy_arn)
    client.put_user_policy(
        UserName=user_name,
        PolicyName=inline_policy_name,
        PolicyDocument=json.dumps(inline_policy_document),
    )
    return client.create_access_key(UserName=user_name)["AccessKey"]


def create_group_with_attached_policy_and_add_user(
    user_name, policy_document, group_name="test-group", policy_name=None
):
    if not policy_name:
        policy_name = str(uuid4())
    client = boto3.client("iam", region_name="us-east-1")
    client.create_group(GroupName=group_name)
    policy_arn = client.create_policy(
        PolicyName=policy_name, PolicyDocument=json.dumps(policy_document)
    )["Policy"]["Arn"]
    client.attach_group_policy(GroupName=group_name, PolicyArn=policy_arn)
    client.add_user_to_group(GroupName=group_name, UserName=user_name)


def create_group_with_inline_policy_and_add_user(
    user_name, policy_document, group_name="test-group", policy_name="policy1"
):
    client = boto3.client("iam", region_name="us-east-1")
    client.create_group(GroupName=group_name)
    client.put_group_policy(
        GroupName=group_name,
        PolicyName=policy_name,
        PolicyDocument=json.dumps(policy_document),
    )
    client.add_user_to_group(GroupName=group_name, UserName=user_name)


def create_group_with_multiple_policies_and_add_user(
    user_name,
    inline_policy_document,
    attached_policy_document,
    group_name="test-group",
    inline_policy_name="policy1",
    attached_policy_name=None,
):
    if not attached_policy_name:
        attached_policy_name = str(uuid4())
    client = boto3.client("iam", region_name="us-east-1")
    client.create_group(GroupName=group_name)
    client.put_group_policy(
        GroupName=group_name,
        PolicyName=inline_policy_name,
        PolicyDocument=json.dumps(inline_policy_document),
    )
    policy_arn = client.create_policy(
        PolicyName=attached_policy_name,
        PolicyDocument=json.dumps(attached_policy_document),
    )["Policy"]["Arn"]
    client.attach_group_policy(GroupName=group_name, PolicyArn=policy_arn)
    client.add_user_to_group(GroupName=group_name, UserName=user_name)


@mock_iam
@mock_sts
def create_role_with_attached_policy_and_assume_it(
    role_name,
    trust_policy_document,
    policy_document,
    session_name="session1",
    policy_name="policy1",
):
    iam_client = boto3.client("iam", region_name="us-east-1")
    sts_client = boto3.client("sts", region_name="us-east-1")
    role_arn = iam_client.create_role(
        RoleName=role_name, AssumeRolePolicyDocument=json.dumps(trust_policy_document)
    )["Role"]["Arn"]
    policy_arn = iam_client.create_policy(
        PolicyName=policy_name, PolicyDocument=json.dumps(policy_document)
    )["Policy"]["Arn"]
    iam_client.attach_role_policy(RoleName=role_name, PolicyArn=policy_arn)
    return sts_client.assume_role(RoleArn=role_arn, RoleSessionName=session_name)[
        "Credentials"
    ]


@mock_iam
@mock_sts
def create_role_with_inline_policy_and_assume_it(
    role_name,
    trust_policy_document,
    policy_document,
    session_name="session1",
    policy_name="policy1",
):
    iam_client = boto3.client("iam", region_name="us-east-1")
    sts_client = boto3.client("sts", region_name="us-east-1")
    role_arn = iam_client.create_role(
        RoleName=role_name, AssumeRolePolicyDocument=json.dumps(trust_policy_document)
    )["Role"]["Arn"]
    iam_client.put_role_policy(
        RoleName=role_name,
        PolicyName=policy_name,
        PolicyDocument=json.dumps(policy_document),
    )
    return sts_client.assume_role(RoleArn=role_arn, RoleSessionName=session_name)[
        "Credentials"
    ]


@set_initial_no_auth_action_count(0)
@mock_iam
def test_invalid_client_token_id():
    client = boto3.client(
        "iam",
        region_name="us-east-1",
        aws_access_key_id="invalid",
        aws_secret_access_key="invalid",
    )
    with pytest.raises(ClientError) as ex:
        client.get_user()
    ex.value.response["Error"]["Code"].should.equal("InvalidClientTokenId")
    ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(403)
    ex.value.response["Error"]["Message"].should.equal(
        "The security token included in the request is invalid."
    )


@set_initial_no_auth_action_count(0)
@mock_ec2
def test_auth_failure():
    client = boto3.client(
        "ec2",
        region_name="us-east-1",
        aws_access_key_id="invalid",
        aws_secret_access_key="invalid",
    )
    with pytest.raises(ClientError) as ex:
        client.describe_instances()
    ex.value.response["Error"]["Code"].should.equal("AuthFailure")
    ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(401)
    ex.value.response["Error"]["Message"].should.equal(
        "AWS was not able to validate the provided access credentials"
    )


@set_initial_no_auth_action_count(2)
@mock_iam
def test_signature_does_not_match():
    access_key = create_user_with_access_key()
    client = boto3.client(
        "iam",
        region_name="us-east-1",
        aws_access_key_id=access_key["AccessKeyId"],
        aws_secret_access_key="invalid",
    )
    with pytest.raises(ClientError) as ex:
        client.get_user()
    ex.value.response["Error"]["Code"].should.equal("SignatureDoesNotMatch")
    ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(403)
    ex.value.response["Error"]["Message"].should.equal(
        "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."
    )


@set_initial_no_auth_action_count(2)
@mock_ec2
def test_auth_failure_with_valid_access_key_id():
    access_key = create_user_with_access_key()
    client = boto3.client(
        "ec2",
        region_name="us-east-1",
        aws_access_key_id=access_key["AccessKeyId"],
        aws_secret_access_key="invalid",
    )
    with pytest.raises(ClientError) as ex:
        client.describe_instances()
    ex.value.response["Error"]["Code"].should.equal("AuthFailure")
    ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(401)
    ex.value.response["Error"]["Message"].should.equal(
        "AWS was not able to validate the provided access credentials"
    )


@set_initial_no_auth_action_count(2)
@mock_ec2
def test_access_denied_with_no_policy():
    user_name = "test-user"
    access_key = create_user_with_access_key(user_name)
    client = boto3.client(
        "ec2",
        region_name="us-east-1",
        aws_access_key_id=access_key["AccessKeyId"],
        aws_secret_access_key=access_key["SecretAccessKey"],
    )
    with pytest.raises(ClientError) as ex:
        client.describe_instances()
    ex.value.response["Error"]["Code"].should.equal("AccessDenied")
    ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(403)
    ex.value.response["Error"]["Message"].should.equal(
        "User: arn:aws:iam::{account_id}:user/{user_name} is not authorized to perform: {operation}".format(
            account_id=ACCOUNT_ID,
            user_name=user_name,
            operation="ec2:DescribeInstances",
        )
    )


@set_initial_no_auth_action_count(3)
@mock_ec2
def test_access_denied_with_not_allowing_policy():
    user_name = "test-user"
    inline_policy_document = {
        "Version": "2012-10-17",
        "Statement": [{"Effect": "Allow", "Action": ["ec2:Run*"], "Resource": "*"}],
    }
    access_key = create_user_with_access_key_and_inline_policy(
        user_name, inline_policy_document
    )
    client = boto3.client(
        "ec2",
        region_name="us-east-1",
        aws_access_key_id=access_key["AccessKeyId"],
        aws_secret_access_key=access_key["SecretAccessKey"],
    )
    with pytest.raises(ClientError) as ex:
        client.describe_instances()
    ex.value.response["Error"]["Code"].should.equal("AccessDenied")
    ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(403)
    ex.value.response["Error"]["Message"].should.equal(
        "User: arn:aws:iam::{account_id}:user/{user_name} is not authorized to perform: {operation}".format(
            account_id=ACCOUNT_ID,
            user_name=user_name,
            operation="ec2:DescribeInstances",
        )
    )


@set_initial_no_auth_action_count(3)
@mock_ec2
def test_access_denied_for_run_instances():
    # https://github.com/spulec/moto/issues/2774
    # The run-instances method was broken between botocore versions 1.15.8 and 1.15.12
    # This was due to the inclusion of '"idempotencyToken":true' in the response, somehow altering the signature and breaking the authentication
    # Keeping this test in place in case botocore decides to break again
    user_name = "test-user"
    inline_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {"Effect": "Allow", "Action": ["ec2:Describe*"], "Resource": "*"}
        ],
    }
    access_key = create_user_with_access_key_and_inline_policy(
        user_name, inline_policy_document
    )
    client = boto3.client(
        "ec2",
        region_name="us-east-1",
        aws_access_key_id=access_key["AccessKeyId"],
        aws_secret_access_key=access_key["SecretAccessKey"],
    )
    with pytest.raises(ClientError) as ex:
        client.run_instances(MaxCount=1, MinCount=1)
    ex.value.response["Error"]["Code"].should.equal("AccessDenied")
    ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(403)
    ex.value.response["Error"]["Message"].should.equal(
        "User: arn:aws:iam::{account_id}:user/{user_name} is not authorized to perform: {operation}".format(
            account_id=ACCOUNT_ID, user_name=user_name, operation="ec2:RunInstances",
        )
    )


@set_initial_no_auth_action_count(3)
@mock_ec2
def test_access_denied_with_denying_policy():
    user_name = "test-user"
    inline_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {"Effect": "Allow", "Action": ["ec2:*"], "Resource": "*"},
            {"Effect": "Deny", "Action": "ec2:CreateVpc", "Resource": "*"},
        ],
    }
    access_key = create_user_with_access_key_and_inline_policy(
        user_name, inline_policy_document
    )
    client = boto3.client(
        "ec2",
        region_name="us-east-1",
        aws_access_key_id=access_key["AccessKeyId"],
        aws_secret_access_key=access_key["SecretAccessKey"],
    )
    with pytest.raises(ClientError) as ex:
        client.create_vpc(CidrBlock="10.0.0.0/16")
    ex.value.response["Error"]["Code"].should.equal("AccessDenied")
    ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(403)
    ex.value.response["Error"]["Message"].should.equal(
        "User: arn:aws:iam::{account_id}:user/{user_name} is not authorized to perform: {operation}".format(
            account_id=ACCOUNT_ID, user_name=user_name, operation="ec2:CreateVpc"
        )
    )


@set_initial_no_auth_action_count(3)
@mock_sts
def test_get_caller_identity_allowed_with_denying_policy():
    user_name = "test-user"
    inline_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {"Effect": "Deny", "Action": "sts:GetCallerIdentity", "Resource": "*"}
        ],
    }
    access_key = create_user_with_access_key_and_inline_policy(
        user_name, inline_policy_document
    )
    client = boto3.client(
        "sts",
        region_name="us-east-1",
        aws_access_key_id=access_key["AccessKeyId"],
        aws_secret_access_key=access_key["SecretAccessKey"],
    )
    client.get_caller_identity().should.be.a(dict)


@set_initial_no_auth_action_count(3)
@mock_ec2
def test_allowed_with_wildcard_action():
    user_name = "test-user"
    inline_policy_document = {