// Copyright IBM Corp. 2020, 2026
// SPDX-License-Identifier: BUSL-1.1

package targets

import (
	"context"
	"fmt"
	"sync"

	"github.com/hashicorp/boundary/globals"
	"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
	"github.com/hashicorp/boundary/internal/session"
	"github.com/hashicorp/boundary/internal/target"
	pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets"
	"google.golang.org/protobuf/proto"
)

// Attributes represent the subtype specific request/response attributes.
type Attributes interface {
	proto.Message

	// Options create target.Options to be used to create a target.Target.
	Options() []target.Option

	// Vet validates the Attributes and returns a map of fields to error messages
	// if any fields are invalid.
	Vet() map[string]string

	// VetForUpdate validates the Attributes for an updated resource with the
	// provided mask paths and returns a map of fields to error messages if any
	// fields are invalid.
	VetForUpdate([]string) map[string]string
}

type attributeFunc func(any) Attributes

type setAttributeFunc func(target.Target, *pb.Target) error

// validateSessionStateFunc validates a session's state for the specific target
// type.
type validateSessionStateFunc func(context.Context, *session.Session) error

type registryEntry struct {
	maskManager              handlers.MaskManager
	attrFunc                 attributeFunc
	setAttrFunc              setAttributeFunc
	validateSessionStateFunc validateSessionStateFunc
}

type registry struct {
	*sync.Map
}

func (r registry) get(s globals.Subtype) (*registryEntry, error) {
	v, ok := r.Load(s)
	if !ok {
		return nil, fmt.Errorf("subtype %q not registered", s)
	}

	re, ok := v.(*registryEntry)
	if !ok {
		return nil, fmt.Errorf("malformed registry subtypye %q registered as incorrect type %T", s, v)
	}
	return re, nil
}

func (r registry) maskManager(s globals.Subtype) (handlers.MaskManager, error) {
	re, err := r.get(s)
	if err != nil {
		return nil, err
	}

	return re.maskManager, nil
}

// newAttribute creates an Attribute for the given subtype. It delegates the
// allocation of the Attribute to the registered attrFunc for the given
// subtype. An error is returned if the provided subtype is not registered
func (r registry) newAttribute(s globals.Subtype, m any) (Attributes, error) {
	re, err := r.get(s)
	if err != nil {
		return nil, err
	}

	attr := re.attrFunc(m)

	return attr, nil
}

// setAttributes is used to set the Attrs field on a pb.Target. It delegates the
// setting of the specific attribute type to the registered setAttributeFunc for
// the given subtype. An error is returned if the provided subtype is not
// registered.
func (r registry) setAttributes(s globals.Subtype, in target.Target, out *pb.Target) error {
	re, err := r.get(s)
	if err != nil {
		return err
	}

	return re.setAttrFunc(in, out)
}

var subtypeRegistry = registry{
	Map: new(sync.Map),
}

// Register registers a subtype for used by the service handler.
func Register(s globals.Subtype, maskManager handlers.MaskManager, af attributeFunc, sf setAttributeFunc, vsf validateSessionStateFunc) {
	if _, existed := subtypeRegistry.LoadOrStore(s, &registryEntry{
		maskManager:              maskManager,
		attrFunc:                 af,
		setAttrFunc:              sf,
		validateSessionStateFunc: vsf,
	}); existed {
		panic(fmt.Sprintf("subtype %s already registered", s))
	}
}
