package ldcomponents

import (
	"time"

	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
	"github.com/launchdarkly/go-server-sdk/v6/internal/datastore"
	"github.com/launchdarkly/go-server-sdk/v6/subsystems"
)

// PersistentDataStoreDefaultCacheTime is the default amount of time that recently read or updated items
// will be cached in memory, if you use [PersistentDataStore]. You can specify otherwise with
// [PersistentDataStoreBuilder.CacheTime].
const PersistentDataStoreDefaultCacheTime = 15 * time.Second

// PersistentDataStore returns a configuration builder for some implementation of a persistent data store.
//
// The return value of this function should be stored in the DataStore field of
// [github.com/launchdarkly/go-server-sdk/v6.Config].
//
// This method is used in conjunction with another configuration builder object provided by specific
// packages such as the Redis integration. Each LaunchDarkly Go SDK database integration has a
// DataStore() method that returns a configuration builder, with builder methods for options that
// are specific to that database. The SDK also provides some universal behaviors for all persistent
// data stores, such as caching; [PersistentDataStoreBuilder] provides methods to configure those
// behaviors. For instance, in this example, URL() is an option that is specific to the Redis
// integration, whereas CacheSeconds is not specific to Redis:
//
//	config := ld.Config{
//	    DataStore: ldcomponents.PersistentDataStore(
//	        ldredis.DataStore().URL("redis://my-redis-host"),
//	    ).CacheSeconds(15),
//	}
//
// See PersistentDataStoreBuilder for more on how this method is used.
//
// For more information on the available persistent data store implementations, see the reference
// guide on "Persistent data stores": https://docs.launchdarkly.com/sdk/concepts/data-stores
func PersistentDataStore(
	persistentDataStoreFactory subsystems.ComponentConfigurer[subsystems.PersistentDataStore],
) *PersistentDataStoreBuilder {
	return &PersistentDataStoreBuilder{
		persistentDataStoreFactory: persistentDataStoreFactory,
		cacheTTL:                   PersistentDataStoreDefaultCacheTime,
	}
}

// PersistentDataStoreBuilder is a configurable factory for a persistent data store.
//
// Each LaunchDarkly Go SDK database integration has its own configuration builder, with builder methods
// for options that are specific to that database. The SDK also provides some universal behaviors for all
// persistent data stores, such as caching; PersistentDataStoreBuilder provides methods to configure those
// behaviors. For instance, in this example, URL() is an option that is specific to the Redis
// integration, whereas CacheSeconds is not specific to Redis:
//
//	config := ld.Config{
//	    DataStore: ldcomponents.PersistentDataStore(
//	        ldredis.DataStore().URL("redis://my-redis-host"),
//	    ).CacheSeconds(15),
//	}
type PersistentDataStoreBuilder struct {
	persistentDataStoreFactory subsystems.ComponentConfigurer[subsystems.PersistentDataStore]
	cacheTTL                   time.Duration
}

// CacheTime specifies the cache TTL. Items will be evicted from the cache after this amount of time
// from the time when they were originally cached.
//
// If the value is zero, caching is disabled (equivalent to [PersistentDataStoreBuilder.NoCaching]).
//
// If the value is negative, data is cached forever (equivalent to [PersistentDataStoreBuilder.CacheForever]).
func (b *PersistentDataStoreBuilder) CacheTime(cacheTime time.Duration) *PersistentDataStoreBuilder {
	b.cacheTTL = cacheTime
	return b
}

// CacheSeconds is a shortcut for calling [PersistentDataStoreBuilder.CacheTime] with a duration in seconds.
func (b *PersistentDataStoreBuilder) CacheSeconds(cacheSeconds int) *PersistentDataStoreBuilder {
	return b.CacheTime(time.Duration(cacheSeconds) * time.Second)
}

// CacheForever specifies that the in-memory cache should never expire. In this mode, data will be
// written to both the underlying persistent store and the cache, but will only ever be read from the
// persistent store if the SDK is restarted.
//
// Use this mode with caution: it means that in a scenario where multiple processes are sharing
// the database, and the current process loses connectivity to LaunchDarkly while other processes
// are still receiving updates and writing them to the database, the current process will have
// stale data.
func (b *PersistentDataStoreBuilder) CacheForever() *PersistentDataStoreBuilder {
	return b.CacheTime(-1 * time.Millisecond)
}

// NoCaching specifies that the SDK should not use an in-memory cache for the persistent data store.
// This means that every feature flag evaluation will trigger a data store query.
func (b *PersistentDataStoreBuilder) NoCaching() *PersistentDataStoreBuilder {
	return b.CacheTime(0)
}

// Build is called internally by the SDK.
func (b *PersistentDataStoreBuilder) Build(clientContext subsystems.ClientContext) (subsystems.DataStore, error) {
	core, err := b.persistentDataStoreFactory.Build(clientContext)
	if err != nil {
		return nil, err
	}
	return datastore.NewPersistentDataStoreWrapper(core, clientContext.GetDataStoreUpdateSink(), b.cacheTTL,
		clientContext.GetLogging().Loggers), nil
}

// DescribeConfiguration is used internally by the SDK to inspect the configuration.
func (b *PersistentDataStoreBuilder) DescribeConfiguration(context subsystems.ClientContext) ldvalue.Value {
	if dd, ok := b.persistentDataStoreFactory.(subsystems.DiagnosticDescription); ok {
		return dd.DescribeConfiguration(context)
	}
	return ldvalue.String("custom")
}
