// Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package iam

import (
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/config"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/aws/aws-sdk-go/service/iam/iamiface"
)

// Client defines methods for interacting with the IAMAPI interface
type Client interface {
	AttachRolePolicy(policyArn, roleName string) (*iam.AttachRolePolicyOutput, error)
	CreateRole(iam.CreateRoleInput) (*iam.CreateRoleOutput, error)
	CreatePolicy(iam.CreatePolicyInput) (*iam.CreatePolicyOutput, error)
	CreateOrFindRole(string, string, string, []*iam.Tag) (string, error)
}

type iamClient struct {
	client iamiface.IAMAPI
}

// NewIAMClient creates an instance of iamClient
func NewIAMClient(config *config.CommandConfig) Client {
	client := iam.New(config.Session)
	client.Handlers.Build.PushBackNamed(clients.CustomUserAgentHandler())

	return newClient(client)
}

func newClient(client iamiface.IAMAPI) Client {
	return &iamClient{
		client: client,
	}
}

func (c *iamClient) AttachRolePolicy(policyArn, roleName string) (*iam.AttachRolePolicyOutput, error) {
	request := iam.AttachRolePolicyInput{
		PolicyArn: aws.String(policyArn),
		RoleName:  aws.String(roleName),
	}

	output, err := c.client.AttachRolePolicy(&request)
	if err != nil {
		return nil, err
	}

	return output, nil
}

func (c *iamClient) CreateRole(input iam.CreateRoleInput) (*iam.CreateRoleOutput, error) {
	output, err := c.client.CreateRole(&input)
	if err != nil {
		return nil, err
	}

	return output, nil
}

func (c *iamClient) CreatePolicy(input iam.CreatePolicyInput) (*iam.CreatePolicyOutput, error) {
	output, err := c.client.CreatePolicy(&input)
	if err != nil {
		return nil, err
	}

	return output, nil
}

// CreateOrFindRole returns a new role ARN or an empty string if role already exists
func (c *iamClient) CreateOrFindRole(roleName, roleDescription, assumeRolePolicyDoc string, tags []*iam.Tag) (string, error) {
	createRoleRequest := iam.CreateRoleInput{
		AssumeRolePolicyDocument: aws.String(assumeRolePolicyDoc),
		Description:              aws.String(roleDescription),
		RoleName:                 aws.String(roleName),
	}
	if len(tags) > 0 {
		createRoleRequest.Tags = tags
	}
	roleResult, err := c.CreateRole(createRoleRequest)
	// if err is b/c role already exists, OK to continue
	if err != nil && !utils.EntityAlreadyExists(err) {
		return "", err
	}
	// TODO: validate AssumeRolePolicyDocument of existing role?

	newRoleString := ""
	if roleResult != nil {
		newRole := *roleResult.Role
		newRoleString = *newRole.Arn
	}
	// TODO, maybe: find & return existing role ARN, plus bool to indicate whether new or not?

	return newRoleString, nil
}
