// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

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

import (
	"context"
	"fmt"
	"log"
	"time"

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

	"github.com/hashicorp/hcl/v2/hcldec"
	"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"
	hcpapi "github.com/hashicorp/packer/internal/hcp/api"
)

type Datasource 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: Fingerprint          string `mapstructure:"fingerprint"`
	// TODO: Label          string `mapstructure:"label"`
}

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

func (d *Datasource) 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
}

// DatasourceOutput is essentially a copy of []*models.HashicorpCloudPackerIteration, but without the
// []Builds or ancestor id.
type DatasourceOutput struct {
	// who created the iteration
	AuthorID string `mapstructure:"author_id"`
	// Name of the bucket that the iteration was retrieved from
	BucketName string `mapstructure:"bucket_name"`
	// If true, this iteration is considered "ready to use" and will be
	// returned even if the include_incomplete flag is "false" in the
	// list iterations request. Note that if you are retrieving an iteration
	// using a channel, this will always be "true"; channels cannot be assigned
	// to incomplete iterations.
	Complete bool `mapstructure:"complete"`
	// The date the iteration was created.
	CreatedAt string `mapstructure:"created_at"`
	// The fingerprint of the build; this could be a git sha or other unique
	// identifier as set by the Packer build that created this iteration.
	Fingerprint string `mapstructure:"fingerprint"`
	// 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 when this iteration was last updated.
	UpdatedAt string `mapstructure:"updated_at"`
	// The ID of the channel used to query the image iteration.
	ChannelID string `mapstructure:"channel_id"`
}

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

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

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

	channel, err := cli.GetChannel(ctx, d.config.Bucket, d.config.Channel)
	if err != nil {
		return cty.NullVal(cty.EmptyObject), fmt.Errorf("error retrieving "+
			"iteration from HCP Packer registry: %s", err.Error())
	}
	if channel.Iteration == nil {
		return cty.NullVal(cty.EmptyObject), fmt.Errorf("there is no iteration associated with the channel %s",
			d.config.Channel)
	}

	iteration := channel.Iteration

	revokeAt := time.Time(iteration.RevokeAt)
	if !revokeAt.IsZero() && revokeAt.Before(time.Now().UTC()) {
		// If RevokeAt is not a zero date and is before NOW, it means this iteration is revoked and should not be used
		// to build new images.
		return cty.NullVal(cty.EmptyObject), fmt.Errorf("the iteration associated with the channel %s is revoked and can not be used on Packer builds",
			d.config.Channel)
	}

	output := DatasourceOutput{
		AuthorID:           iteration.AuthorID,
		BucketName:         iteration.BucketSlug,
		Complete:           iteration.Complete,
		CreatedAt:          iteration.CreatedAt.String(),
		Fingerprint:        iteration.Fingerprint,
		ID:                 iteration.ID,
		IncrementalVersion: iteration.IncrementalVersion,
		UpdatedAt:          iteration.UpdatedAt.String(),
		ChannelID:          channel.ID,
	}

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