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

package server

import (
	"context"

	"github.com/fatih/structs"
	"github.com/hashicorp/boundary/internal/db/timestamp"
	"github.com/hashicorp/boundary/internal/errors"
	"github.com/hashicorp/boundary/internal/server/store"
	"github.com/hashicorp/boundary/sdk/pbs/plugin"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/structpb"
)

type (
	WorkerType        string
	OperationalState  string
	LocalStorageState string
)

const (
	UnknownWorkerType                     WorkerType        = "unknown"
	KmsWorkerType                         WorkerType        = "kms"
	PkiWorkerType                         WorkerType        = "pki"
	ActiveOperationalState                OperationalState  = "active"
	ShutdownOperationalState              OperationalState  = "shutdown"
	UnknownOperationalState               OperationalState  = "unknown"
	AvailableLocalStorageState            LocalStorageState = "available"
	LowStorageLocalStorageState           LocalStorageState = "low storage"
	CriticallyLowStorageLocalStorageState LocalStorageState = "critically low storage"
	OutOfStorageLocalStorageState         LocalStorageState = "out of storage"
	NotConfiguredLocalStorageState        LocalStorageState = "not configured"
	UnknownLocalStorageState              LocalStorageState = "unknown"
)

func (t WorkerType) Valid() bool {
	switch t {
	case KmsWorkerType, PkiWorkerType:
		return true
	}
	return false
}

func (t WorkerType) String() string {
	switch t {
	case KmsWorkerType, PkiWorkerType:
		return string(t)
	}
	return string(UnknownWorkerType)
}

type workerAuthWorkerId struct {
	WorkerId string `mapstructure:"worker_id"`
}

func ValidOperationalState(s string) bool {
	switch s {
	case ActiveOperationalState.String(), ShutdownOperationalState.String():
		return true
	}
	return false
}

func (t OperationalState) String() string {
	switch t {
	case ActiveOperationalState, ShutdownOperationalState:
		return string(t)
	}
	return string(UnknownOperationalState)
}

func ValidLocalStorageState(s string) bool {
	switch s {
	case AvailableLocalStorageState.String(), LowStorageLocalStorageState.String(),
		CriticallyLowStorageLocalStorageState.String(), OutOfStorageLocalStorageState.String(),
		NotConfiguredLocalStorageState.String(), UnknownLocalStorageState.String():
		return true
	}
	return false
}

func (t LocalStorageState) String() string {
	switch t {
	case AvailableLocalStorageState, LowStorageLocalStorageState,
		OutOfStorageLocalStorageState, NotConfiguredLocalStorageState,
		CriticallyLowStorageLocalStorageState:
		return string(t)
	}
	return string(UnknownLocalStorageState)
}

// AttachWorkerIdToState accepts a workerId and creates a struct for use with the Nodeenrollment lib
// This is intended for use in worker authorization; AuthorizeNode in the lib accepts the option WithState
// so that the workerId is passed through to storage and associated with a WorkerAuth record
func AttachWorkerIdToState(ctx context.Context, workerId string) (*structpb.Struct, error) {
	const op = "server.(Worker).AttachWorkerIdToState"
	if workerId == "" {
		return nil, errors.New(ctx, errors.InvalidParameter, op, "missing workerId")
	}

	workerMap := &workerAuthWorkerId{WorkerId: workerId}
	s := structs.New(workerMap)
	s.TagName = "mapstructure"
	return structpb.NewStruct(s.Map())
}

// A Worker is a server that provides an address which can be used to proxy
// session connections. It can be tagged with custom tags and is used when
// authorizing and establishing a session.  It is owned by a scope.
type Worker struct {
	*store.Worker
	ApiTags               Tags   `json:"api_tags" gorm:"->"`
	ConfigTags            Tags   `json:"config_tags" gorm:"->"`
	ActiveConnectionCount uint32 `gorm:"->"`

	// inputTags is not specified to be api or config tags and is not intended
	// to be read by clients.  Since config tags and api tags are applied in
	// mutually exclusive contexts, inputTags is interpreted to be one or the
	// other based on the context in which the worker is passed.  As such
	// inputTags should only be read when performing mutations on the database.
	inputTags []*Tag `gorm:"-"`

	// This is used to pass the token back to the calling function
	ControllerGeneratedActivationToken string `gorm:"-"`

	// RemoteStorageStates is a map of storage buckets and their storage bucket credential states
	RemoteStorageStates map[string]*plugin.StorageBucketCredentialState `gorm:"-"`
}

// NewWorker returns a new Worker. Valid options are WithName, WithDescription
// WithAddress, and WithWorkerTags. All other options are ignored.  This does
// not set any of the worker reported values.
func NewWorker(scopeId string, opt ...Option) *Worker {
	opts := GetOpts(opt...)
	worker := &Worker{
		Worker: &store.Worker{
			ScopeId:           scopeId,
			Name:              opts.withName,
			Description:       opts.withDescription,
			Address:           opts.withAddress,
			ReleaseVersion:    opts.withReleaseVersion,
			OperationalState:  opts.withOperationalState,
			LocalStorageState: opts.withLocalStorageState,
		},
		inputTags: opts.withWorkerTags,
	}
	if opts.withTestUseInputTagsAsApiTags {
		worker.ApiTags = convertToTags(worker.inputTags)
	}
	return worker
}

// allocWorker will allocate a Worker
func allocWorker() Worker {
	return Worker{Worker: &store.Worker{}}
}

func (w *Worker) clone() *Worker {
	if w == nil {
		return nil
	}
	cw := proto.Clone(w.Worker)
	cWorker := &Worker{
		Worker: cw.(*store.Worker),
	}
	if w.ApiTags != nil {
		cWorker.ApiTags = w.ApiTags.clone()
	}
	if w.ConfigTags != nil {
		cWorker.ConfigTags = w.ConfigTags.clone()
	}
	if w.inputTags != nil {
		cWorker.inputTags = make([]*Tag, 0, len(w.inputTags))
		for _, t := range w.inputTags {
			cWorker.inputTags = append(cWorker.inputTags, &Tag{Key: t.Key, Value: t.Value})
		}
	}
	return cWorker
}

// CanonicalTags is the deduplicated set of tags contained on both the resource
// set over the API as well as the tags reported by the worker itself. This
// function is guaranteed to return a non-nil map.
func (w *Worker) CanonicalTags() Tags {
	return compactTags(&w.ApiTags, &w.ConfigTags)
}

// GetLastStatusTime contains the last time the worker has reported to the
// controller its connection status.  If the worker has never reported to a
// controller then nil is returned.
func (w *Worker) GetLastStatusTime() *timestamp.Timestamp {
	if w == nil || w.Worker == nil || w.Worker.GetLastStatusTime().AsTime() == timestamp.NegativeInfinityTS {
		return nil
	}
	return w.Worker.GetLastStatusTime()
}

// TableName overrides the table name used by Worker to `server_worker`
func (Worker) TableName() string {
	return "server_worker"
}
