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

package server

import (
	"context"
	"crypto/rand"
	"io"
	"time"

	"github.com/hashicorp/boundary/internal/db"
	"github.com/hashicorp/boundary/version"
	"github.com/hashicorp/nodeenrollment/types"
)

// getOpts - iterate the inbound Options and return a struct
func GetOpts(opt ...Option) options {
	opts := getDefaultOptions()
	for _, o := range opt {
		o(&opts)
	}
	return opts
}

// Option - how Options are passed as arguments
type Option func(*options)

// StorageBucketCredentialInfo defines the parameters to pass into the
// WithFilterWorkersByStorageBucketCredentialState option.
type StorageBucketCredentialInfo struct {
	CredentialId string
	Filters      []FilterStorageBucketCredentialStateFn
}

// options = how options are represented
type options struct {
	withName                                        string
	withPublicId                                    string
	withDescription                                 string
	withAddress                                     string
	withLimit                                       int
	withLiveness                                    time.Duration
	withUpdateTags                                  bool
	withWorkerTags                                  []*Tag
	withWorkerKeyIdentifier                         string
	withWorkerKeys                                  WorkerKeys
	withControllerEncryptionPrivateKey              []byte
	withKeyId                                       string
	withNonce                                       []byte
	withNewIdFunc                                   func(context.Context) (string, error)
	WithFetchNodeCredentialsRequest                 *types.FetchNodeCredentialsRequest
	withTestPkiWorkerAuthorized                     bool
	withTestPkiWorkerKeyId                          *string
	withTestUseInputTagsAsApiTags                   bool
	withWorkerType                                  WorkerType
	withRoot                                        RootInfo
	withStopAfter                                   uint
	WithCreateControllerLedActivationToken          bool
	withReleaseVersion                              string
	withOperationalState                            string
	withLocalStorageState                           string
	withActiveWorkers                               bool
	withFeature                                     version.Feature
	withDirectlyConnected                           bool
	withWorkerPool                                  []string
	withFilterWorkersByStorageBucketCredentialState *StorageBucketCredentialInfo
	withFilterWorkersByLocalStorageState            bool
	WithReader                                      db.Reader
	WithWriter                                      db.Writer
	withRandomReader                                io.Reader
}

func getDefaultOptions() options {
	return options{
		withNewIdFunc:         newWorkerId,
		withOperationalState:  ActiveOperationalState.String(),
		withLocalStorageState: UnknownWorkerType.String(),
		withRandomReader:      rand.Reader,
	}
}

// WithDescription provides an optional description.
func WithDescription(desc string) Option {
	return func(o *options) {
		o.withDescription = desc
	}
}

// WithName provides an optional name.
func WithName(name string) Option {
	return func(o *options) {
		o.withName = name
	}
}

// WithPublicId provides an optional public Id used for skipping one db call.
func WithPublicId(id string) Option {
	return func(o *options) {
		o.withPublicId = id
	}
}

// WithAddress provides an optional address.
func WithAddress(address string) Option {
	return func(o *options) {
		o.withAddress = address
	}
}

// WithLimit provides an option to provide a limit.  Intentionally allowing
// negative integers.   If WithLimit < 0, then unlimited results are returned.
// If WithLimit == 0, then default limits are used for results.
func WithLimit(limit int) Option {
	return func(o *options) {
		o.withLimit = limit
	}
}

// WithLiveness indicates how far back we want to search for server entries.
// Use 0 for the default liveness (15 seconds). A liveness value of -1 removes
// the liveliness condition.
func WithLiveness(liveness time.Duration) Option {
	return func(o *options) {
		o.withLiveness = liveness
	}
}

// WithUpdateTags indicates that we should perform tag updates in the DB.
// This would happen on first sync from a worker after startup or (eventually,
// perhaps), after a SIGHUP.
func WithUpdateTags(updateTags bool) Option {
	return func(o *options) {
		o.withUpdateTags = updateTags
	}
}

// WithWorkerTags provides worker tags.
func WithWorkerTags(tags ...*Tag) Option {
	return func(o *options) {
		newTags := []*Tag{}
		for _, tag := range tags {
			if tag != nil {
				newTags = append(newTags, tag)
			}
		}
		if len(newTags) > 0 {
			o.withWorkerTags = newTags
		}
	}
}

func WithWorkerKeyIdentifier(workerKeyIdentifier string) Option {
	return func(o *options) {
		o.withWorkerKeyIdentifier = workerKeyIdentifier
	}
}

func WithWorkerKeys(workerKeys WorkerKeys) Option {
	return func(o *options) {
		o.withWorkerKeys = workerKeys
	}
}

func WithControllerEncryptionPrivateKey(controllerKey []byte) Option {
	return func(o *options) {
		o.withControllerEncryptionPrivateKey = controllerKey
	}
}

func WithKeyId(keyId string) Option {
	return func(o *options) {
		o.withKeyId = keyId
	}
}

func WithNonce(nonce []byte) Option {
	return func(o *options) {
		o.withNonce = nonce
	}
}

// WithNewIdFunc allows an optional factory function for new worker IDs to be
// specified (this option is likely only useful for tests).
func WithNewIdFunc(fn func(context.Context) (string, error)) Option {
	return func(o *options) {
		o.withNewIdFunc = fn
	}
}

// WithFetchNodeCredentialsRequest allows an optional
// FetchNodeCredentialsRequest to be specified.
func WithFetchNodeCredentialsRequest(req *types.FetchNodeCredentialsRequest) Option {
	return func(o *options) {
		o.WithFetchNodeCredentialsRequest = req
	}
}

// WithTestPkiWorkerAuthorizedKeyId should only be used in tests.
// It specifies that the test worker should be authorized when returned and
// assigns the key id for that worker to the string pointer in this option.
func WithTestPkiWorkerAuthorizedKeyId(id *string) Option {
	return func(o *options) {
		o.withTestPkiWorkerAuthorized = true
		o.withTestPkiWorkerKeyId = id
	}
}

// WithTestUseInputTagsAsApiTags tells NewWorker to set the set of input tags as
// the api tags as well. This is useful for allowing a worker to have canonical
// tags without having to store worker information in the database.
func WithTestUseInputTagsAsApiTags(with bool) Option {
	return func(o *options) {
		o.withTestUseInputTagsAsApiTags = with
	}
}

// WithWorkerType allows specifying a particular type of worker (kms, pki)
// during lookup or listing
func WithWorkerType(with WorkerType) Option {
	return func(o *options) {
		o.withWorkerType = with
	}
}

// WithRoot provides an optional root node
func WithRoot(root RootInfo) Option {
	return func(o *options) {
		o.withRoot = root
	}
}

// WithStopAfter provides an optional stop after count
func WithStopAfter(stopAfter uint) Option {
	return func(o *options) {
		o.withStopAfter = stopAfter
	}
}

// WithCreateControllerLedActivationToken provides an optional stop after count
func WithCreateControllerLedActivationToken(with bool) Option {
	return func(o *options) {
		o.WithCreateControllerLedActivationToken = with
	}
}

// WithRelease version provides an optional release version
func WithReleaseVersion(version string) Option {
	return func(o *options) {
		o.withReleaseVersion = version
	}
}

// WithOperationalState provides an optional operational state.
func WithOperationalState(state string) Option {
	return func(o *options) {
		o.withOperationalState = state
	}
}

// WithActiveWorkers provides an optional filter to only include active workers
func WithActiveWorkers(withActive bool) Option {
	return func(o *options) {
		o.withActiveWorkers = withActive
	}
}

// WithFeature provides an option to specify a filter
func WithFeature(feature version.Feature) Option {
	return func(o *options) {
		o.withFeature = feature
	}
}

// WithDirectlyConnected provides an option to limit graph search to only directly connected workers
func WithDirectlyConnected(conn bool) Option {
	return func(o *options) {
		o.withDirectlyConnected = conn
	}
}

// WithWorkerPool provides a slice of worker ids.
func WithWorkerPool(workerIds []string) Option {
	return func(o *options) {
		o.withWorkerPool = workerIds
	}
}

// WithLocalStorageState provides an optional local storage state.
func WithLocalStorageState(state string) Option {
	return func(o *options) {
		o.withLocalStorageState = state
	}
}

// WithFilterWorkersByStorageBucketCredentialState receives a storage bucket
// credential id and filters to apply and calls
// FilterWorkersByStorageBucketCredentialState in supported repository
// functions.
func WithFilterWorkersByStorageBucketCredentialState(ci *StorageBucketCredentialInfo) Option {
	return func(o *options) {
		o.withFilterWorkersByStorageBucketCredentialState = ci
	}
}

// WithFilterWorkersByLocalStorageState controls whether
// FilterWorkersByLocalStorageState is called in supported repository functions.
func WithFilterWorkersByLocalStorageState(filter bool) Option {
	return func(o *options) {
		o.withFilterWorkersByLocalStorageState = filter
	}
}

// WithReaderWriter is used to share the same database reader
// and writer when executing sql within a transaction.
func WithReaderWriter(r db.Reader, w db.Writer) Option {
	return func(o *options) {
		o.WithReader = r
		o.WithWriter = w
	}
}

// WithRandomReader provides an option to specify a random reader.
func WithRandomReader(reader io.Reader) Option {
	return func(o *options) {
		o.withRandomReader = reader
	}
}
