package yandex

import (
	"context"
	"errors"
	"fmt"

	"github.com/hashicorp/packer/packer-plugin-sdk/multistep"
	packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"

	"github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1"
	ycsdk "github.com/yandex-cloud/go-sdk"
)

type StepInstanceInfo struct{}

func (s *StepInstanceInfo) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
	sdk := state.Get("sdk").(*ycsdk.SDK)
	ui := state.Get("ui").(packersdk.Ui)
	c := state.Get("config").(*Config)
	instanceID := state.Get("instance_id").(string)

	ui.Say(fmt.Sprintf("Waiting for instance with id %s to become active...", instanceID))

	ctx, cancel := context.WithTimeout(ctx, c.StateTimeout)
	defer cancel()

	instance, err := sdk.Compute().Instance().Get(ctx, &compute.GetInstanceRequest{
		InstanceId: instanceID,
		View:       compute.InstanceView_FULL,
	})
	if err != nil {
		return StepHaltWithError(state, fmt.Errorf("Error retrieving instance data: %s", err))
	}

	instanceIP, err := getInstanceIPAddress(c, instance)
	if err != nil {
		return StepHaltWithError(state, fmt.Errorf("Failed to find instance ip address: %s", err))
	}

	state.Put("instance_ip", instanceIP)
	ui.Message(fmt.Sprintf("Detected instance IP: %s", instanceIP))

	return multistep.ActionContinue
}

func getInstanceIPAddress(c *Config, instance *compute.Instance) (address string, err error) {
	// Instance could have several network interfaces with different configuration each
	// Get all possible addresses for instance
	addrIPV4Internal, addrIPV4External, addrIPV6Addr, err := instanceAddresses(instance)
	if err != nil {
		return "", err
	}

	if c.UseIPv6 {
		if addrIPV6Addr != "" {
			return "[" + addrIPV6Addr + "]", nil
		}
		return "", errors.New("instance has no one IPv6 address")
	}

	if c.UseInternalIP {
		if addrIPV4Internal != "" {
			return addrIPV4Internal, nil
		}
		return "", errors.New("instance has no one IPv4 internal address")
	}
	if addrIPV4External != "" {
		return addrIPV4External, nil
	}
	return "", errors.New("instance has no one IPv4 external address")
}

func instanceAddresses(instance *compute.Instance) (ipV4Int, ipV4Ext, ipV6 string, err error) {
	if len(instance.NetworkInterfaces) == 0 {
		return "", "", "", errors.New("No one network interface found for an instance")
	}

	var ipV4IntFound, ipV4ExtFound, ipV6Found bool
	for _, iface := range instance.NetworkInterfaces {
		if !ipV6Found && iface.PrimaryV6Address != nil {
			ipV6 = iface.PrimaryV6Address.Address
			ipV6Found = true
		}

		if !ipV4IntFound && iface.PrimaryV4Address != nil {
			ipV4Int = iface.PrimaryV4Address.Address
			ipV4IntFound = true

			if !ipV4ExtFound && iface.PrimaryV4Address.OneToOneNat != nil {
				ipV4Ext = iface.PrimaryV4Address.OneToOneNat.Address
				ipV4ExtFound = true
			}
		}

		if ipV6Found && ipV4IntFound && ipV4ExtFound {
			break
		}
	}

	if !ipV4IntFound {
		// internal ipV4 address always should present
		return "", "", "", errors.New("No IPv4 internal address found. Bug?")
	}

	return
}

func (s *StepInstanceInfo) Cleanup(state multistep.StateBag) {
	// no cleanup
}
