package policy

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/iam"
)

// allowed actions
var (
	RWNamespaceIndexActions = []interface{}{
		"dynamodb:PutItem",
		"dynamodb:Query",
		"dynamodb:Scan",
		"dynamodb:DescribeTable",
		"dynamodb:UpdateItem",
	}
	RONamespaceIndexActions = []interface{}{
		"dynamodb:Query",
	}
	RWNamespaceTableActions = RWNamespaceIndexActions
	ROKMSActions            = []interface{}{
		"kms:Decrypt",
	}
	RWKMSActions = []interface{}{
		"kms:Decrypt",
		"kms:GenerateDataKey",
	}
	ROSecretsTableActions = []interface{}{
		"dynamodb:Query",
		"dynamodb:DescribeTable",
		"dynamodb:GetItem",
	}
	RWSecretsTableActions = []interface{}{
		"dynamodb:PutItem",
		"dynamodb:Query",
		"dynamodb:Scan",
		"dynamodb:DescribeTable",
		"dynamodb:UpdateItem",
	}
)

// AttachedEntities list of users, groups and roles attached with a policy.
type AttachedEntities struct {
	//PolicyArn aws policy arn
	PolicyArn string

	// A list of IAM groups that the policy is attached to.
	PolicyGroups []*iam.PolicyGroup

	// A list of IAM roles that the policy is attached to.
	PolicyRoles []*iam.PolicyRole

	// A list of IAM users that the policy is attached to.
	PolicyUsers []*iam.PolicyUser
}

// NamespacePolicyStatement returns a policy statement allowing access to the
// sandstorm namespace table
func (pg *IAMPolicyGenerator) NamespacePolicyStatement(namespaces []string, writeAccess bool) IAMPolicyStatement {
	if writeAccess {
		return IAMPolicyStatement{
			Action:   RWNamespaceTableActions,
			Resource: []string{pg.DynamoDBNamespaceTableArn},
			Effect:   "Allow",
			Condition: IAMStatementCondition{
				"ForAllValues:StringEquals": map[string]interface{}{
					"dynamodb:LeadingKeys": namespaces,
				},
			},
			Sid: NamespaceTableSid,
		}
	}
	return IAMPolicyStatement{}
}

// NamespaceIndexPolicyStatement returns a policy statement allowing access to
// the namespace index
func (pg *IAMPolicyGenerator) NamespaceIndexPolicyStatement(namespaces []string, writeAccess bool) IAMPolicyStatement {
	var actions []interface{}
	switch writeAccess {
	case true:
		actions = RWNamespaceIndexActions
	case false:
		actions = RONamespaceIndexActions
	}
	return IAMPolicyStatement{
		Action:   actions,
		Resource: []string{fmt.Sprintf("%s/index/namespace_name", pg.DynamoDBSecretsTableArn)},
		Effect:   "Allow",
		Condition: IAMStatementCondition{
			"ForAllValues:StringEquals": map[string]interface{}{
				"dynamodb:LeadingKeys": namespaces,
			},
		},
		Sid: NamespaceIndexSid,
	}
}

// SecretsPolicyStatement returns a policy statement granting access to the
// secrets table
func (pg *IAMPolicyGenerator) SecretsPolicyStatement(secretKeys []string, writeAccess bool) IAMPolicyStatement {
	var actions []interface{}
	var resourceArns []string
	switch writeAccess {
	case true:
		actions = RWSecretsTableActions
		resourceArns = []string{
			pg.DynamoDBSecretsTableArn,
			pg.DynamoDBSecretsAuditTableArn,
		}
	case false:
		actions = ROSecretsTableActions
		resourceArns = []string{
			pg.DynamoDBSecretsTableArn,
		}
	}

	return IAMPolicyStatement{
		Action:   actions,
		Resource: resourceArns,
		Effect:   "Allow",
		Condition: IAMStatementCondition{
			"ForAllValues:StringLike": map[string]interface{}{
				"dynamodb:LeadingKeys": secretKeys,
			},
		},
		Sid: SecretsTableSid,
	}
}

// AssumeRolePolicyStatement returns a policy statement allowing a role to assume itself
func (pg *IAMPolicyGenerator) AssumeRolePolicyStatement(roleARN string) IAMPolicyStatement {
	return IAMPolicyStatement{
		Action:   []string{"sts:AssumeRole"},
		Effect:   "Allow",
		Resource: []string{roleARN},
	}
}

// DetachRolePolicy detach a policy from role.
func (pg *IAMPolicyGenerator) DetachRolePolicy(policyArn string, roleName string) (*iam.DetachRolePolicyOutput, error) {
	return pg.IAM.DetachRolePolicy(&iam.DetachRolePolicyInput{
		PolicyArn: aws.String(policyArn),
		RoleName:  aws.String(roleName),
	})
}

// AttachRolePolicy Attach a policy from role.
func (pg *IAMPolicyGenerator) AttachRolePolicy(policyArn string, roleName string) (*iam.AttachRolePolicyOutput, error) {

	attachAuxPolicyInput := &iam.AttachRolePolicyInput{
		RoleName:  aws.String(roleName),
		PolicyArn: aws.String(policyArn),
	}
	return pg.IAM.AttachRolePolicy(attachAuxPolicyInput)
}

// DetachGroupPolicy detach a policy from role.
func (pg *IAMPolicyGenerator) DetachGroupPolicy(policyArn string, groupName string) (*iam.DetachGroupPolicyOutput, error) {
	return pg.IAM.DetachGroupPolicy(&iam.DetachGroupPolicyInput{
		PolicyArn: aws.String(policyArn),
		GroupName: aws.String(groupName),
	})
}

// AttachGroupPolicy Attach a policy from role.
func (pg *IAMPolicyGenerator) AttachGroupPolicy(policyArn string, groupName string) (*iam.AttachGroupPolicyOutput, error) {

	attachAuxPolicyInput := &iam.AttachGroupPolicyInput{
		GroupName: aws.String(groupName),
		PolicyArn: aws.String(policyArn),
	}
	return pg.IAM.AttachGroupPolicy(attachAuxPolicyInput)
}

//ListEntitiesAttachedToPolicies return list of groups, roles and users attached to a policy
func (pg *IAMPolicyGenerator) ListEntitiesAttachedToPolicies(policyArn string) (*AttachedEntities, error) {

	if policyArn == "" {
		return nil, fmt.Errorf("Please provide policy arn")
	}

	inputParams := &iam.ListEntitiesForPolicyInput{
		PolicyArn: aws.String(policyArn),
	}

	attachedEntities := &AttachedEntities{}
	attachedEntities.PolicyArn = policyArn

	var resp *iam.ListEntitiesForPolicyOutput
	var err error
	for {
		resp, err = pg.IAM.ListEntitiesForPolicy(inputParams)
		if err != nil {
			return nil, err
		}

		attachedEntities.PolicyUsers = append(attachedEntities.PolicyUsers, resp.PolicyUsers...)
		attachedEntities.PolicyGroups = append(attachedEntities.PolicyGroups, resp.PolicyGroups...)
		attachedEntities.PolicyRoles = append(attachedEntities.PolicyRoles, resp.PolicyRoles...)

		if resp.IsTruncated != nil && *resp.IsTruncated {
			inputParams.Marker = resp.Marker
		} else {
			break
		}
	}
	return attachedEntities, err
}
