package cvm

import (
	"context"
	"fmt"
	"regexp"
	"strings"
	"time"

	"github.com/hashicorp/packer/common/retry"
	"github.com/hashicorp/packer/helper/multistep"
	"github.com/hashicorp/packer/packer"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
	cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
)

// DefaultWaitForInterval is sleep interval when wait statue
const DefaultWaitForInterval = 5

// WaitForInstance wait for instance reaches statue
func WaitForInstance(client *cvm.Client, instanceId string, status string, timeout int) error {
	ctx := context.TODO()
	req := cvm.NewDescribeInstancesRequest()
	req.InstanceIds = []*string{&instanceId}

	for {
		var resp *cvm.DescribeInstancesResponse
		err := Retry(ctx, func(ctx context.Context) error {
			var e error
			resp, e = client.DescribeInstances(req)
			return e
		})
		if err != nil {
			return err
		}
		if *resp.Response.TotalCount == 0 {
			return fmt.Errorf("instance(%s) not exist", instanceId)
		}
		if *resp.Response.InstanceSet[0].InstanceState == status {
			break
		}
		time.Sleep(DefaultWaitForInterval * time.Second)
		timeout = timeout - DefaultWaitForInterval
		if timeout <= 0 {
			return fmt.Errorf("wait instance(%s) status(%s) timeout", instanceId, status)
		}
	}

	return nil
}

// WaitForImageReady wait for image reaches statue
func WaitForImageReady(client *cvm.Client, imageName string, status string, timeout int) error {
	ctx := context.TODO()
	req := cvm.NewDescribeImagesRequest()
	FILTER_IMAGE_NAME := "image-name"
	req.Filters = []*cvm.Filter{
		{
			Name:   &FILTER_IMAGE_NAME,
			Values: []*string{&imageName},
		},
	}

	for {
		var resp *cvm.DescribeImagesResponse
		err := Retry(ctx, func(ctx context.Context) error {
			var e error
			resp, e = client.DescribeImages(req)
			return e
		})
		if err != nil {
			return err
		}
		find := false
		for _, image := range resp.Response.ImageSet {
			if *image.ImageName == imageName && *image.ImageState == status {
				find = true
				break
			}
		}
		if find {
			break
		}
		time.Sleep(DefaultWaitForInterval * time.Second)
		timeout = timeout - DefaultWaitForInterval
		if timeout <= 0 {
			return fmt.Errorf("wait image(%s) status(%s) timeout", imageName, status)
		}
	}

	return nil
}

// CheckResourceIdFormat check resource id format
func CheckResourceIdFormat(resource string, id string) bool {
	regex := regexp.MustCompile(fmt.Sprintf("%s-[0-9a-z]{8}$", resource))
	if !regex.MatchString(id) {
		return false
	}
	return true
}

// SSHHost returns a function that can be given to the SSH communicator
func SSHHost(pubilcIp bool) func(multistep.StateBag) (string, error) {
	return func(state multistep.StateBag) (string, error) {
		instance := state.Get("instance").(*cvm.Instance)
		if pubilcIp {
			return *instance.PublicIpAddresses[0], nil
		} else {
			return *instance.PrivateIpAddresses[0], nil
		}
	}
}

// Retry do retry on api request
func Retry(ctx context.Context, fn func(context.Context) error) error {
	return retry.Config{
		Tries: 30,
		ShouldRetry: func(err error) bool {
			e, ok := err.(*errors.TencentCloudSDKError)
			if !ok {
				return false
			}
			if e.Code == "ClientError.NetworkError" || e.Code == "ClientError.HttpStatusCodeError" ||
				e.Code == "InvalidKeyPair.NotSupported" || e.Code == "InvalidInstance.NotSupported" ||
				strings.Contains(e.Code, "RequestLimitExceeded") || strings.Contains(e.Code, "InternalError") ||
				strings.Contains(e.Code, "ResourceInUse") || strings.Contains(e.Code, "ResourceBusy") {
				return true
			}
			return false
		},
		RetryDelay: (&retry.Backoff{
			InitialBackoff: 1 * time.Second,
			MaxBackoff:     5 * time.Second,
			Multiplier:     2,
		}).Linear,
	}.Run(ctx, fn)
}

// SayClean tell you clean module message
func SayClean(state multistep.StateBag, module string) {
	_, halted := state.GetOk(multistep.StateHalted)
	_, cancelled := state.GetOk(multistep.StateCancelled)
	if halted {
		Say(state, fmt.Sprintf("Deleting %s because of error...", module), "")
	} else if cancelled {
		Say(state, fmt.Sprintf("Deleting %s because of cancellation...", module), "")
	} else {
		Say(state, fmt.Sprintf("Cleaning up %s...", module), "")
	}
}

// Say tell you a message
func Say(state multistep.StateBag, message, prefix string) {
	if prefix != "" {
		message = fmt.Sprintf("%s: %s", prefix, message)
	}

	if strings.HasPrefix(message, "Trying to") {
		message += "..."
	}

	ui := state.Get("ui").(packer.Ui)
	ui.Say(message)
}

// Message print a message
func Message(state multistep.StateBag, message, prefix string) {
	if prefix != "" {
		message = fmt.Sprintf("%s: %s", prefix, message)
	}

	ui := state.Get("ui").(packer.Ui)
	ui.Message(message)
}

// Error print error message
func Error(state multistep.StateBag, err error, prefix string) {
	if prefix != "" {
		err = fmt.Errorf("%s: %s", prefix, err)
	}

	ui := state.Get("ui").(packer.Ui)
	ui.Error(err.Error())
}

// Halt print error message and exit
func Halt(state multistep.StateBag, err error, prefix string) multistep.StepAction {
	Error(state, err, prefix)
	state.Put("error", err)

	return multistep.ActionHalt
}