/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/

package client

import (
	"context"
	"fmt"

	"cloud.google.com/go/compute/apiv1/computepb"
	"google.golang.org/protobuf/proto"
)

// GetNodeImage returns the image name of the node.
func (c *Client) GetNodeImage(ctx context.Context, providerID string) (string, error) {
	project, zone, instanceName, err := splitProviderID(providerID)
	if err != nil {
		return "", err
	}
	project, err = c.canonicalProjectID(ctx, project)
	if err != nil {
		return "", err
	}
	instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
		Instance: instanceName,
		Project:  project,
		Zone:     zone,
	})
	if err != nil {
		return "", err
	}
	// first disk is always the boot disk
	if len(instance.Disks) < 1 {
		return "", fmt.Errorf("instance %v has no disks", instanceName)
	}
	if instance.Disks[0] == nil || instance.Disks[0].Source == nil {
		return "", fmt.Errorf("instance %q has invalid disk", instanceName)
	}
	diskReq, err := diskSourceToDiskReq(*instance.Disks[0].Source)
	if err != nil {
		return "", err
	}
	disk, err := c.diskAPI.Get(ctx, diskReq)
	if err != nil {
		return "", err
	}
	if disk.SourceImage == nil {
		return "", fmt.Errorf("disk %q has no source image", diskReq.Disk)
	}
	return uriNormalize(*disk.SourceImage), nil
}

// GetScalingGroupID returns the scaling group ID of the node.
func (c *Client) GetScalingGroupID(ctx context.Context, providerID string) (string, error) {
	project, zone, instanceName, err := splitProviderID(providerID)
	if err != nil {
		return "", err
	}
	instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
		Instance: instanceName,
		Project:  project,
		Zone:     zone,
	})
	if err != nil {
		return "", fmt.Errorf("getting instance %q: %w", instanceName, err)
	}
	scalingGroupID := getMetadataByKey(instance.Metadata, "created-by")
	if scalingGroupID == "" {
		return "", fmt.Errorf("instance %q has no created-by metadata", instanceName)
	}
	scalingGroupID, err = c.canonicalInstanceGroupID(ctx, scalingGroupID)
	if err != nil {
		return "", err
	}
	return scalingGroupID, nil
}

// CreateNode creates a node in the specified scaling group.
func (c *Client) CreateNode(ctx context.Context, scalingGroupID string) (nodeName, providerID string, err error) {
	project, zone, instanceGroupName, err := splitInstanceGroupID(scalingGroupID)
	if err != nil {
		return "", "", err
	}
	project, err = c.canonicalProjectID(ctx, project)
	if err != nil {
		return "", "", err
	}
	instanceGroupManager, err := c.instanceGroupManagersAPI.Get(ctx, &computepb.GetInstanceGroupManagerRequest{
		InstanceGroupManager: instanceGroupName,
		Project:              project,
		Zone:                 zone,
	})
	if err != nil {
		return "", "", err
	}
	if instanceGroupManager.BaseInstanceName == nil {
		return "", "", fmt.Errorf("instance group manager %q has no base instance name", instanceGroupName)
	}
	instanceName := generateInstanceName(*instanceGroupManager.BaseInstanceName, c.prng)
	op, err := c.instanceGroupManagersAPI.CreateInstances(ctx, &computepb.CreateInstancesInstanceGroupManagerRequest{
		InstanceGroupManager: instanceGroupName,
		Project:              project,
		Zone:                 zone,
		InstanceGroupManagersCreateInstancesRequestResource: &computepb.InstanceGroupManagersCreateInstancesRequest{
			Instances: []*computepb.PerInstanceConfig{
				{Name: proto.String(instanceName)},
			},
		},
	})
	if err != nil {
		return "", "", err
	}
	if err := op.Wait(ctx); err != nil {
		return "", "", err
	}
	return instanceName, joinProviderID(project, zone, instanceName), nil
}

// DeleteNode deletes a node specified by its provider ID.
func (c *Client) DeleteNode(ctx context.Context, providerID string) error {
	_, zone, instanceName, err := splitProviderID(providerID)
	if err != nil {
		return err
	}
	scalingGroupID, err := c.GetScalingGroupID(ctx, providerID)
	if err != nil {
		return err
	}
	instanceGroupProject, instanceGroupZone, instanceGroupName, err := splitInstanceGroupID(scalingGroupID)
	if err != nil {
		return err
	}
	instanceID := joinInstanceID(zone, instanceName)
	op, err := c.instanceGroupManagersAPI.DeleteInstances(ctx, &computepb.DeleteInstancesInstanceGroupManagerRequest{
		InstanceGroupManager: instanceGroupName,
		Project:              instanceGroupProject,
		Zone:                 instanceGroupZone,
		InstanceGroupManagersDeleteInstancesRequestResource: &computepb.InstanceGroupManagersDeleteInstancesRequest{
			Instances: []string{instanceID},
		},
	})
	if err != nil {
		return fmt.Errorf("deleting instance %q from instance group manager %q: %w", instanceID, scalingGroupID, err)
	}
	return op.Wait(ctx)
}
