// Copyright IBM Corp. 2024, 2025
// SPDX-License-Identifier: BUSL-1.1

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

import (
	"context"
	"fmt"
	"log"

	hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models"
	"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 bucket name in the HCP Packer Registry.
	BucketName string `mapstructure:"bucket_name" required:"true"`
	// The channel name in the given bucket to use for retrieving the version.
	ChannelName string `mapstructure:"channel_name" required:"true"`
}

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.BucketName == "" {
		errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("the `bucket_name` must be specified"))
	}
	if d.config.ChannelName == "" {
		errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("the `channel_name` must be specified"))
	}

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

// DatasourceOutput is essentially a copy of []*models.HashicorpCloudPacker20230101Version, but without
// the build and ancestry details
type DatasourceOutput struct {
	// Name of the author who created this version.
	AuthorID string `mapstructure:"author_id"`

	// The name of the bucket that this version is associated with.
	BucketName string `mapstructure:"bucket_name"`

	// Current state of the version.
	Status string `mapstructure:"status"`

	// The date the version was created.
	CreatedAt string `mapstructure:"created_at"`

	// The fingerprint of the version; this is a  unique identifier set by the Packer build
	// that created this version.
	Fingerprint string `mapstructure:"fingerprint"`

	// The version ID. This is a ULID, which is a unique identifier similar
	// to a UUID. It is created by the HCP Packer Registry when a version is
	// first created, and is unique to this version.
	ID string `mapstructure:"id"`

	// The version name is created by the HCP Packer Registry once a version is
	// "complete". Incomplete or failed versions currently default to having a name "v0".
	Name string `mapstructure:"name"`

	// The date when this version was last updated.
	UpdatedAt string `mapstructure:"updated_at"`

	// The ID of the channel used to query this version.
	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
	}
	log.Printf(
		"[INFO] Reading HCP Packer Version info from HCP Packer Registry (%s) "+
			"[project_id=%s, organization_id=%s, channel=%s]",
		d.config.BucketName, cli.ProjectID, cli.OrganizationID, d.config.ChannelName,
	)

	channel, err := cli.GetChannel(ctx, d.config.BucketName, d.config.ChannelName)
	if err != nil {
		return cty.NullVal(cty.EmptyObject), fmt.Errorf(
			"error retrieving HCP Packer Version from HCP Packer Registry: %s",
			err.Error(),
		)
	}
	if channel.Version == nil {
		return cty.NullVal(cty.EmptyObject), fmt.Errorf(
			"there is no HCP Packer Version associated with the channel %s",
			d.config.ChannelName,
		)
	}

	version := channel.Version

	if *version.Status == hcpPackerModels.HashicorpCloudPacker20230101VersionStatusVERSIONREVOKED {
		return cty.NullVal(cty.EmptyObject), fmt.Errorf(
			"the HCP Packer Version associated with the channel %s is revoked and can not be used on Packer builds",
			d.config.ChannelName,
		)
	}

	output := DatasourceOutput{
		AuthorID:    version.AuthorID,
		BucketName:  version.BucketName,
		Status:      string(*version.Status),
		CreatedAt:   version.CreatedAt.String(),
		Fingerprint: version.Fingerprint,
		ID:          version.ID,
		Name:        version.Name,
		UpdatedAt:   version.UpdatedAt.String(),
		ChannelID:   channel.ID,
	}

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