import json

import boto3
import pytest
from botocore.exceptions import ClientError

from moto import mock_ssoadmin
from moto.iam.aws_managed_policies import aws_managed_policies_data

# See our Development Tips on writing tests for hints on how to write good tests:
# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html

DUMMY_PERMISSIONSET_ID = (
    "arn:aws:sso:::permissionSet/ins-eeeeffffgggghhhh/ps-hhhhkkkkppppoooo"
)
DUMMY_INSTANCE_ARN = "arn:aws:sso:::instance/ins-aaaabbbbccccdddd"


@pytest.fixture(name="managed_policies")
def get_managed_policies():
    return json.loads(aws_managed_policies_data)


def create_permissionset(client) -> str:
    """Helper function to create a dummy permission set and returns the arn."""

    response = client.create_permission_set(
        Name="test-permission-set",
        InstanceArn=DUMMY_INSTANCE_ARN,
        Description="test permission set",
    )

    return response["PermissionSet"]["PermissionSetArn"]


@mock_ssoadmin
def test_put_inline_policy_to_permission_set():
    """
    Tests putting and getting an inline policy to a permission set.
    """
    client = boto3.client("sso-admin", region_name="us-east-1")

    permission_set_arn = create_permissionset(client)
    dummy_policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::your-bucket-name/*",
            }
        ],
    }

    # Happy path
    response = client.put_inline_policy_to_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        InlinePolicy=json.dumps(dummy_policy),
    )

    response = client.get_inline_policy_for_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
    )

    assert response["InlinePolicy"] == json.dumps(dummy_policy)

    # Invalid permission set arn
    not_create_ps_arn = (
        "arn:aws:sso:::permissionSet/ins-eeeeffffgggghhhh/ps-hhhhkkkkppppoxyz"
    )
    with pytest.raises(ClientError) as e:
        client.put_inline_policy_to_permission_set(
            InstanceArn=DUMMY_INSTANCE_ARN,
            PermissionSetArn=not_create_ps_arn,
            InlinePolicy=json.dumps(dummy_policy),
        )
    err = e.value.response["Error"]
    assert err["Code"] == "ResourceNotFoundException"
    assert err["Message"] == "Could not find PermissionSet with id ps-hhhhkkkkppppoxyz"


@mock_ssoadmin
def test_get_inline_policy_to_permission_set_no_policy():
    client = boto3.client("sso-admin", region_name="us-east-1")

    permission_set_arn = create_permissionset(client)

    response = client.get_inline_policy_for_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
    )

    assert response["InlinePolicy"] == ""


@mock_ssoadmin
def test_delete_inline_policy_to_permissionset():
    client = boto3.client("sso-admin", region_name="us-east-1")

    permission_set_arn = create_permissionset(client)

    dummy_policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::your-bucket-name/*",
            }
        ],
    }

    client.put_inline_policy_to_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        InlinePolicy=json.dumps(dummy_policy),
    )

    response = client.get_inline_policy_for_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
    )

    assert response["InlinePolicy"] == json.dumps(dummy_policy)

    client.delete_inline_policy_from_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
    )

    response = client.get_inline_policy_for_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
    )

    assert response["InlinePolicy"] == ""


@mock_ssoadmin
def test_attach_managed_policy_to_permission_set():
    client = boto3.client("sso-admin", region_name="us-east-1")

    permission_set_arn = create_permissionset(client)
    permissionset_id = permission_set_arn.split("/")[-1]
    managed_policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"

    client.attach_managed_policy_to_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        ManagedPolicyArn=managed_policy_arn,
    )

    response = client.list_managed_policies_in_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
    )

    assert response["AttachedManagedPolicies"][0]["Name"] == "AdministratorAccess"
    assert (
        response["AttachedManagedPolicies"][0]["Arn"]
        == "arn:aws:iam::aws:policy/AdministratorAccess"
    )

    # test for managed policy that is already attached
    with pytest.raises(ClientError) as e:
        client.attach_managed_policy_to_permission_set(
            InstanceArn=DUMMY_INSTANCE_ARN,
            PermissionSetArn=permission_set_arn,
            ManagedPolicyArn=managed_policy_arn,
        )
    err = e.value.response["Error"]
    assert err["Code"] == "ConflictException"
    assert (
        err["Message"]
        == f"Permission set with id {permissionset_id} already has a typed link attachment to a manged policy with {managed_policy_arn}"
    )

    # test for managed policy that does not exist
    not_exist_managed_policy_arn = "arn:aws:iam::aws:policy/DoesNotExist"
    with pytest.raises(ClientError) as e:
        client.attach_managed_policy_to_permission_set(
            InstanceArn=DUMMY_INSTANCE_ARN,
            PermissionSetArn=permission_set_arn,
            ManagedPolicyArn=not_exist_managed_policy_arn,
        )
    err = e.value.response["Error"]
    assert err["Code"] == "ResourceNotFoundException"
    assert (
        err["Message"]
        == "Policy does not exist with ARN: arn:aws:iam::aws:policy/DoesNotExist"
    )


@mock_ssoadmin
def test_list_managed_policies_quota_limit(managed_policies):
    """
    Tests exceeding the managed policy quota limit.
    """
    managed_policies_to_attach = []
    policy_count = 0
    for policy_name in managed_policies:
        path = managed_policies[policy_name]["Path"]
        # only attach policies with path "/"
        if path != "/":
            continue
        managed_policies_to_attach.append(policy_name)
        policy_count += 1
        if policy_count >= 21:  # 20 is the quota limit
            break

    client = boto3.client("sso-admin", region_name="us-east-1")
    permission_set_arn = create_permissionset(client)
    permission_set_id = permission_set_arn.split("/")[-1]

    arn_string = "arn:aws:iam::aws:policy/"
    with pytest.raises(ClientError) as e:
        # the 21st policy should exceed the quota limit
        for managed_policy in managed_policies_to_attach:
            client.attach_managed_policy_to_permission_set(
                InstanceArn=DUMMY_INSTANCE_ARN,
                PermissionSetArn=permission_set_arn,
                ManagedPolicyArn=arn_string + managed_policy,
            )
    err = e.value.response["Error"]
    assert err["Code"] == "ServiceQuotaExceededException"
    assert (
        err["Message"]
        == f"You have exceeded AWS SSO limits. Cannot create ManagedPolicy more than 20 for id {permission_set_id}. Please refer to https://docs.aws.amazon.com/singlesignon/latest/userguide/limits.html"
    )


@mock_ssoadmin
def test_list_managed_policies_in_permission_set(managed_policies):
    """
    Tests functionality of listing aws managed policies attached to a permission set.
    This also tests the pagination functionality.
    """
    client = boto3.client("sso-admin", region_name="us-east-1")

    arn_string = "arn:aws:iam::aws:policy/"

    # create a dummy permission set
    permission_set_arn = create_permissionset(client)

    managed_policies_names = list(managed_policies.keys())

    # attach 3 good managed policies
    for idx in range(3):
        managed_policy_name = managed_policies_names[idx]

        client.attach_managed_policy_to_permission_set(
            InstanceArn=DUMMY_INSTANCE_ARN,
            PermissionSetArn=permission_set_arn,
            ManagedPolicyArn=arn_string + managed_policy_name,
        )

    response = client.list_managed_policies_in_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        MaxResults=2,
    )

    managed_policies = []

    assert len(response["AttachedManagedPolicies"]) == 2
    managed_policies.extend(response["AttachedManagedPolicies"])
    next_token = response["NextToken"]

    response = client.list_managed_policies_in_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        MaxResults=2,
        NextToken=next_token,
    )

    assert len(response["AttachedManagedPolicies"]) == 1
    managed_policies.extend(response["AttachedManagedPolicies"])

    # ensure the 3 unique managed policies were returned
    actual_managed_policy_names = [
        managed_policy["Name"] for managed_policy in managed_policies
    ]
    expected_managed_policy_names = managed_policies_names[:3]
    assert all(
        name in actual_managed_policy_names for name in expected_managed_policy_names
    )


@mock_ssoadmin
def test_detach_managed_policy_from_permission_set():
    client = boto3.client("sso-admin", region_name="us-east-1")
    permission_set_arn = create_permissionset(client)
    managed_policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"

    # test for managed policy that is not attached
    with pytest.raises(ClientError) as e:
        client.detach_managed_policy_from_permission_set(
            InstanceArn=DUMMY_INSTANCE_ARN,
            PermissionSetArn=permission_set_arn,
            ManagedPolicyArn=managed_policy_arn,
        )
    err = e.value.response["Error"]
    assert err["Code"] == "ResourceNotFoundException"
    assert (
        err["Message"] == f"Could not find ManagedPolicy with arn {managed_policy_arn}"
    )

    # attach managed policy
    client.attach_managed_policy_to_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        ManagedPolicyArn=managed_policy_arn,
    )

    # detach managed policy
    client.detach_managed_policy_from_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        ManagedPolicyArn=managed_policy_arn,
    )

    # ensure managed policy is detached
    response = client.list_managed_policies_in_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
    )

    assert len(response["AttachedManagedPolicies"]) == 0


@mock_ssoadmin
def test_attach_customer_managed_policy_reference_to_permission_set():
    client = boto3.client("sso-admin", region_name="us-east-1")
    permission_set_arn = create_permissionset(client)

    policy_name = "test-policy"
    policy_path = "/test-path/"

    client.attach_customer_managed_policy_reference_to_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        CustomerManagedPolicyReference={
            "Name": policy_name,
            "Path": policy_path,
        },
    )

    response = client.list_customer_managed_policy_references_in_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
    )

    assert len(response["CustomerManagedPolicyReferences"]) == 1
    assert response["CustomerManagedPolicyReferences"][0]["Name"] == policy_name
    assert response["CustomerManagedPolicyReferences"][0]["Path"] == policy_path

    # test for customer managed policy that is already attached
    with pytest.raises(ClientError) as e:
        client.attach_customer_managed_policy_reference_to_permission_set(
            InstanceArn=DUMMY_INSTANCE_ARN,
            PermissionSetArn=permission_set_arn,
            CustomerManagedPolicyReference={
                "Name": policy_name,
                "Path": policy_path,
            },
        )
    err = e.value.response["Error"]
    assert err["Code"] == "ConflictException"
    assert (
        err["Message"]
        == f"Given customer managed policy with name: {policy_name}  and path {policy_path} already attached"
    )


@mock_ssoadmin
def test_list_customer_managed_policy_references_in_permission_set():
    """
    Tests listing customer managed policies including pagination.
    """
    client = boto3.client("sso-admin", region_name="us-east-1")
    permission_set_arn = create_permissionset(client)

    policy_name = "test-policy-"

    # attach 3 customer managed policies
    for idx in range(3):
        client.attach_customer_managed_policy_reference_to_permission_set(
            InstanceArn=DUMMY_INSTANCE_ARN,
            PermissionSetArn=permission_set_arn,
            CustomerManagedPolicyReference={"Name": f"{policy_name}{idx}"},
        )

    response = client.list_customer_managed_policy_references_in_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        MaxResults=2,
    )

    customer_managed_policy_names = []

    assert len(response["CustomerManagedPolicyReferences"]) == 2
    next_token = response["NextToken"]
    for name in response["CustomerManagedPolicyReferences"]:
        customer_managed_policy_names.append(name["Name"])

    response = client.list_customer_managed_policy_references_in_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        MaxResults=2,
        NextToken=next_token,
    )
    for name in response["CustomerManagedPolicyReferences"]:
        customer_managed_policy_names.append(name["Name"])

    assert len(response["CustomerManagedPolicyReferences"]) == 1

    # ensure the 3 unique customer managed policies were returned
    assert len(set(customer_managed_policy_names)) == 3


@mock_ssoadmin
def test_detach_customer_managed_policy_reference_from_permission_set():
    client = boto3.client("sso-admin", region_name="us-east-1")
    permission_set_arn = create_permissionset(client)

    # trying to detach a policy that doesn't exist yet
    with pytest.raises(ClientError) as e:
        client.detach_customer_managed_policy_reference_from_permission_set(
            InstanceArn=DUMMY_INSTANCE_ARN,
            PermissionSetArn=permission_set_arn,
            CustomerManagedPolicyReference={
                "Name": "test-policy",
            },
        )
    err = e.value.response["Error"]
    assert err["Code"] == "ResourceNotFoundException"
    assert (
        err["Message"]
        == "Given managed policy with name: test-policy  and path / does not exist on PermissionSet"
    )

    # attach a policy
    client.attach_customer_managed_policy_reference_to_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        CustomerManagedPolicyReference={
            "Name": "test-policy",
            "Path": "/some-path/",
        },
    )

    # try to detach the policy but default path (should fail)
    with pytest.raises(ClientError) as e:
        client.detach_customer_managed_policy_reference_from_permission_set(
            InstanceArn=DUMMY_INSTANCE_ARN,
            PermissionSetArn=permission_set_arn,
            CustomerManagedPolicyReference={
                "Name": "test-policy",
            },
        )

    # detach the policy
    client.detach_customer_managed_policy_reference_from_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
        CustomerManagedPolicyReference={
            "Name": "test-policy",
            "Path": "/some-path/",
        },
    )

    # ensure policy is detached
    response = client.list_customer_managed_policy_references_in_permission_set(
        InstanceArn=DUMMY_INSTANCE_ARN,
        PermissionSetArn=permission_set_arn,
    )

    assert len(response["CustomerManagedPolicyReferences"]) == 0
