/*
Copyright (c) Edgeless Systems GmbH

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

package client

import (
	"context"
	"math/rand"
	"time"

	compute "cloud.google.com/go/compute/apiv1"
	"github.com/spf13/afero"
	"go.uber.org/multierr"
)

// Client is a client for the Google Compute Engine.
type Client struct {
	projectID string
	projectAPI
	instanceAPI
	instanceTemplateAPI
	instanceGroupManagersAPI
	diskAPI
	// prng is a pseudo-random number generator seeded with time. Not used for security.
	prng
}

// New creates a new client for the Google Compute Engine.
func New(ctx context.Context, configPath string) (*Client, error) {
	projectID, err := loadProjectID(afero.NewOsFs(), configPath)
	if err != nil {
		return nil, err
	}

	var closers []closer
	projectAPI, err := compute.NewProjectsRESTClient(ctx)
	if err != nil {
		return nil, err
	}

	closers = append(closers, projectAPI)
	insAPI, err := compute.NewInstancesRESTClient(ctx)
	if err != nil {
		_ = closeAll(closers)
		return nil, err
	}
	closers = append(closers, insAPI)
	templAPI, err := compute.NewInstanceTemplatesRESTClient(ctx)
	if err != nil {
		_ = closeAll(closers)
		return nil, err
	}
	closers = append(closers, templAPI)
	groupAPI, err := compute.NewInstanceGroupManagersRESTClient(ctx)
	if err != nil {
		_ = closeAll(closers)
		return nil, err
	}
	closers = append(closers, groupAPI)
	diskAPI, err := compute.NewDisksRESTClient(ctx)
	if err != nil {
		_ = closeAll(closers)
		return nil, err
	}
	return &Client{
		projectID:                projectID,
		projectAPI:               projectAPI,
		instanceAPI:              insAPI,
		instanceTemplateAPI:      &instanceTemplateClient{templAPI},
		instanceGroupManagersAPI: &instanceGroupManagersClient{groupAPI},
		diskAPI:                  diskAPI,
		prng:                     rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
	}, nil
}

// Close closes the client's connection.
func (c *Client) Close() error {
	closers := []closer{
		c.projectAPI,
		c.instanceAPI,
		c.instanceTemplateAPI,
		c.instanceGroupManagersAPI,
		c.diskAPI,
	}
	return closeAll(closers)
}

type closer interface {
	Close() error
}

// closeAll closes all closers, even if an error occurs.
//
// Errors are collected and a composed error is returned.
func closeAll(closers []closer) error {
	// Since this function is intended to be deferred, it will always call all
	// close operations, even if a previous operation failed.
	var errs error
	for _, closer := range closers {
		errs = multierr.Append(errs, closer.Close())
	}
	return errs
}