package arm

import (
	"context"
	"fmt"
	"time"

	"github.com/hashicorp/packer/builder/azure/common/constants"
	"github.com/hashicorp/packer/common/retry"
	"github.com/hashicorp/packer/helper/multistep"
	"github.com/hashicorp/packer/packer"
)

const (
	maxResourcesToDelete = 50
)

type StepDeleteResourceGroup struct {
	client *AzureClient
	delete func(ctx context.Context, state multistep.StateBag, resourceGroupName string) error
	say    func(message string)
	error  func(e error)
}

func NewStepDeleteResourceGroup(client *AzureClient, ui packer.Ui) *StepDeleteResourceGroup {
	var step = &StepDeleteResourceGroup{
		client: client,
		say:    func(message string) { ui.Say(message) },
		error:  func(e error) { ui.Error(e.Error()) },
	}

	step.delete = step.deleteResourceGroup
	return step
}

func (s *StepDeleteResourceGroup) deleteResourceGroup(ctx context.Context, state multistep.StateBag, resourceGroupName string) error {
	var err error
	if state.Get(constants.ArmIsExistingResourceGroup).(bool) {
		s.say("\nThe resource group was not created by Packer, only deleting individual resources ...")
		var deploymentName = state.Get(constants.ArmDeploymentName).(string)
		err = s.deleteDeploymentResources(ctx, deploymentName, resourceGroupName)
		if err != nil {
			return err
		}

		if keyVaultDeploymentName, ok := state.GetOk(constants.ArmKeyVaultDeploymentName); ok {
			// Only delete if custom keyvault was not provided.
			if exists := state.Get(constants.ArmIsExistingKeyVault).(bool); !exists {
				s.say("\n Deleting the keyvault deployment because it was created by Packer...")
				err = s.deleteDeploymentResources(ctx, keyVaultDeploymentName.(string), resourceGroupName)
				if err != nil {
					return err
				}
			}
		}

		return nil
	} else {
		s.say("\nThe resource group was created by Packer, deleting ...")
		f, err := s.client.GroupsClient.Delete(ctx, resourceGroupName)
		if err == nil {
			if state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
				// No need to wait for the completion for delete if request is Accepted
				s.say(fmt.Sprintf("\nResource Group is being deleted, not waiting for deletion due to config. Resource Group Name '%s'", resourceGroupName))
			} else {
				f.WaitForCompletionRef(ctx, s.client.GroupsClient.Client)
			}

		}

		if err != nil {
			s.say(s.client.LastError.Error())
		}
		return err
	}
}

func (s *StepDeleteResourceGroup) deleteDeploymentResources(ctx context.Context, deploymentName, resourceGroupName string) error {
	maxResources := int32(maxResourcesToDelete)

	deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(ctx, resourceGroupName, deploymentName, &maxResources)
	if err != nil {
		s.reportIfError(err, resourceGroupName)
		return err
	}

	for deploymentOperations.NotDone() {
		deploymentOperation := deploymentOperations.Value()
		// Sometimes an empty operation is added to the list by Azure
		if deploymentOperation.Properties.TargetResource == nil {
			deploymentOperations.Next()
			continue
		}

		resourceName := *deploymentOperation.Properties.TargetResource.ResourceName
		resourceType := *deploymentOperation.Properties.TargetResource.ResourceType

		s.say(fmt.Sprintf(" -> %s : '%s'",
			resourceType,
			resourceName))

		retry.Config{
			Tries:      10,
			RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 600 * time.Second, Multiplier: 2}).Linear,
		}.Run(ctx, func(ctx context.Context) error {
			err := deleteResource(ctx, s.client,
				resourceType,
				resourceName,
				resourceGroupName)
			if err != nil {
				s.reportIfError(err, resourceName)
			}
			return nil
		})

		if err = deploymentOperations.Next(); err != nil {
			return err
		}
	}

	return nil
}

func (s *StepDeleteResourceGroup) reportIfError(err error, resourceName string) {
	if err != nil {
		s.say(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
			"Name: %s\n"+
			"Error: %s", resourceName, err.Error()))
		s.error(err)
	}
}

func (s *StepDeleteResourceGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
	s.say("Deleting resource group ...")

	var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
	s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))

	err := s.delete(ctx, state, resourceGroupName)
	if err != nil {
		state.Put(constants.Error, err)
		s.error(err)

		return multistep.ActionHalt
	}

	state.Put(constants.ArmIsResourceGroupCreated, false)

	return multistep.ActionContinue
}

func (*StepDeleteResourceGroup) Cleanup(multistep.StateBag) {
}
