package client

import (
	"context"
	"fmt"

	iampb "google.golang.org/genproto/googleapis/iam/v1"
)

// addIAMPolicyBindings adds a GCP service account to roles specified in the input.
func (c *Client) addIAMPolicyBindings(ctx context.Context, input AddIAMPolicyBindingInput) error {
	getReq := &iampb.GetIamPolicyRequest{
		Resource: "projects/" + c.project,
	}
	policy, err := c.projectsAPI.GetIamPolicy(ctx, getReq)
	if err != nil {
		return fmt.Errorf("retrieving current iam policy failed: %w", err)
	}
	for _, binding := range input.Bindings {
		addIAMPolicy(policy, binding)
	}
	setReq := &iampb.SetIamPolicyRequest{
		Resource: "projects/" + c.project,
		Policy:   policy,
	}
	if _, err := c.projectsAPI.SetIamPolicy(ctx, setReq); err != nil {
		return fmt.Errorf("setting new iam policy failed: %w", err)
	}
	return nil
}

// PolicyBinding is a GCP IAM policy binding.
type PolicyBinding struct {
	ServiceAccount string
	Role           string
}

// addIAMPolicy inserts policy binding for service account and role to an existing iam policy.
func addIAMPolicy(policy *iampb.Policy, policyBinding PolicyBinding) {
	var binding *iampb.Binding
	for _, existingBinding := range policy.Bindings {
		if existingBinding.Role == policyBinding.Role && existingBinding.Condition == nil {
			binding = existingBinding
			break
		}
	}
	if binding == nil {
		binding = &iampb.Binding{
			Role: policyBinding.Role,
		}
		policy.Bindings = append(policy.Bindings, binding)
	}

	// add service account to role, if not already a member
	member := "serviceAccount:" + policyBinding.ServiceAccount
	var alreadyMember bool
	for _, existingMember := range binding.Members {
		if member == existingMember {
			alreadyMember = true
			break
		}
	}
	if !alreadyMember {
		binding.Members = append(binding.Members, member)
	}
}

// AddIAMPolicyBindingInput is the input for an AddIAMPolicyBinding operation.
type AddIAMPolicyBindingInput struct {
	Bindings []PolicyBinding
}