//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type DatasourceOutput,Config,ParBuild,ParImage
package packer_image_iteration

import (
	"context"
	"fmt"
	"log"

	"github.com/zclconf/go-cty/cty"

	"github.com/hashicorp/hcl/v2/hcldec"
	"github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/preview/2021-04-30/models"
	"github.com/hashicorp/packer-plugin-sdk/common"
	"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
	packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
	"github.com/hashicorp/packer-plugin-sdk/template/config"
	packerregistry "github.com/hashicorp/packer/internal/registry"
)

// Type for Packer datasource has been renamed temporarily to prevent it from being
// automatically registered as a viable datasource plugin in command/plugin.go.
// In the future this type will be renamed to allow for the use of the datasource.
type DeactivatedDatasource struct {
	config Config
}

type Config struct {
	common.PackerConfig `mapstructure:",squash"`
	// The name of the bucket your image is in.
	Bucket string `mapstructure:"bucket_name" required:"true"`
	// The name of the channel to use when retrieving your image
	Channel string `mapstructure:"channel" required:"true"`
	// TODO: Version          string `mapstructure:"version"`
	// TODO: Label          string `mapstructure:"label"`
}

func (d *DeactivatedDatasource) ConfigSpec() hcldec.ObjectSpec {
	return d.config.FlatMapstructure().HCL2Spec()
}

func (d *DeactivatedDatasource) Configure(raws ...interface{}) error {
	err := config.Decode(&d.config, nil, raws...)
	if err != nil {
		return err
	}

	var errs *packersdk.MultiError

	if d.config.Bucket == "" {
		errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("The `bucket_name` must be specified"))
	}
	if d.config.Channel == "" {
		errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`channel` is currently a required field."))
	}

	if errs != nil && len(errs.Errors) > 0 {
		return errs
	}
	return nil
}

// Copy of []*models.HashicorpCloudPackerBuild. Need to copy so we can generate
// the HCL spec.
type ParBuild struct {
	// The name of the cloud provider that the build exists in. For example,
	// "aws", "azure", or "gce".
	CloudProvider string `mapstructure:"cloud_provider"`
	// The specific Packer builder or post-processor used to create the build.
	ComponentType string `mapstructure:"component_type"`
	// The date and time at which the build was run.
	CreatedAt string `mapstructure:"created_at"`
	// The build id. This is a ULID, which is a unique identifier similar
	// to a UUID. It is created by the HCP Packer Registry when an build is
	// first created, and is unique to this build.
	ID string `mapstructure:"id"`
	// A list of images as stored in the HCP Packer registry. See the ParImage
	// docs for more information.
	Images []ParImage `mapstructure:"images"`
	// The iteration id. This is a ULID, which is a unique identifier similar
	// to a UUID. It is created by the HCP Packer Registry when an iteration is
	// first created, and is unique to this iteration.
	IterationID string `mapstructure:"iteration_id"`
	// Unstructured key:value metadata associated with the build.
	Labels map[string]string `mapstructure:"labels"`
	// The UUID associated with the Packer run that created this build.
	PackerRunUUID string `mapstructure:"packer_run_uuid"`
	// Whether the build is considered "complete" (the Packer build ran
	// successfully and created an artifact), or "incomplete" (the Packer
	// build did not finish, and there is no uploaded artifact).
	Status string `mapstructure:"status"`
	// The date and time at which the build was last updated.
	UpdatedAt string `mapstructure:"updated_at"`
}

// Copy of []*models.HashicorpCloudPackerImage Need to copy so we can generate
// the HCL spec.
type ParImage struct {
	// The date and time at which the build was last updated.
	CreatedAt string `mapstructure:"created_at,omitempty"`
	// The iteration id. This is a ULID, which is a unique identifier similar
	// to a UUID. It is created by the HCP Packer Registry when an iteration is
	// first created, and is unique to this iteration.
	ID string `mapstructure:"id,omitempty"`
	// ID or URL of the remote cloud image as given by a build.
	ImageID string `mapstructure:"image_id,omitempty"`
	// The cloud region as given by `packer build`. eg. "ap-east-1".
	// For locally managed clouds, this may map instead to a cluster, server
	// or datastore.
	Region string `mapstructure:"region,omitempty"`
}

type DatasourceOutput struct {
	// The iteration id. This is a ULID, which is a unique identifier similar
	// to a UUID. It is created by the HCP Packer Registry when an iteration is
	// first created, and is unique to this iteration.
	Id string `mapstructure:"Id"`
	// The version number assigned to an iteration. This number is an integer,
	// and is created by the HCP Packer Registry once an iteration is
	// marked "complete". If a new iteration is marked "complete", the version
	// that HCP Packer assigns to it will always be the highest previous
	// iteration version plus one.
	IncrementalVersion int32 `mapstructure:"incremental_version"`
	// The date the iteration was created.
	CreatedAt string `mapstructure:"created_at"`
	// A list of builds that are stored in the iteration. These builds can be
	// parsed using HCL to find individual image ids for specific providers.
	Builds []ParBuild `mapstructure:"builds"`
}

func (d *DeactivatedDatasource) OutputSpec() hcldec.ObjectSpec {
	return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec()
}

func (d *DeactivatedDatasource) Execute() (cty.Value, error) {
	ctx := context.TODO()

	cli, err := packerregistry.NewClient()
	if err != nil {
		return cty.NullVal(cty.EmptyObject), err
	}
	// Load channel.
	log.Printf("[INFO] Reading info from HCP Packer registry (%s) [project_id=%s, organization_id=%s, channel=%s]",
		d.config.Bucket, cli.ProjectID, cli.OrganizationID, d.config.Channel)

	iteration, err := cli.GetIterationFromChannel(ctx, d.config.Bucket, d.config.Channel)
	if err != nil {
		return cty.NullVal(cty.EmptyObject), fmt.Errorf("error retrieving "+
			"image iteration from HCP Packer registry: %s", err.Error())
	}
	output := DatasourceOutput{
		IncrementalVersion: iteration.IncrementalVersion,
		CreatedAt:          iteration.CreatedAt.String(),
		Builds:             convertPackerBuildList(iteration.Builds),
	}

	return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil
}

func convertPackerBuildList(builds []*models.HashicorpCloudPackerBuild) (flattened []ParBuild) {
	for _, build := range builds {
		out := ParBuild{
			CloudProvider: build.CloudProvider,
			ComponentType: build.ComponentType,
			CreatedAt:     build.CreatedAt.String(),
			ID:            build.ID,
			Images:        convertPackerBuildImagesList(build.Images),
			Labels:        build.Labels,
			PackerRunUUID: build.PackerRunUUID,
			Status:        string(build.Status),
			UpdatedAt:     build.UpdatedAt.String(),
		}
		flattened = append(flattened, out)
	}
	return
}

func convertPackerBuildImagesList(images []*models.HashicorpCloudPackerImage) (flattened []ParImage) {
	for _, image := range images {
		out := ParImage{
			CreatedAt: image.CreatedAt.String(),
			ID:        image.ID,
			ImageID:   image.ImageID,
			Region:    image.Region,
		}
		flattened = append(flattened, out)
	}
	return
}
