package setting

import (
	"context"
	"github.com/cloudreve/Cloudreve/v4/inventory"
	"github.com/cloudreve/Cloudreve/v4/pkg/cache"
	"github.com/cloudreve/Cloudreve/v4/pkg/conf"
	"github.com/cloudreve/Cloudreve/v4/pkg/logging"
	"github.com/samber/lo"
	"os"
	"strings"
)

const (
	KvSettingPrefix           = "setting_"
	EnvSettingOverwritePrefix = "CR_SETTING_"
)

// SettingStoreAdapter chains a setting get operation, if current adapter cannot locate setting value,
// it will invoke next adapter until last one.
type SettingStoreAdapter interface {
	// Get a string setting from underlying store, if setting not found, default
	// value will be used.
	Get(ctx context.Context, name string, defaultVal any) any
}

// NewDbSettingStore creates an adapter using DB setting store. Only string type value is supported.
func NewDbSettingStore(c inventory.SettingClient, next SettingStoreAdapter) SettingStoreAdapter {
	return &dbSettingStore{
		c:    c,
		next: next,
	}
}

// NewKvSettingStore creates an adapter using KV setting store.
func NewKvSettingStore(c cache.Driver, next SettingStoreAdapter) SettingStoreAdapter {
	return &kvSettingStore{
		kv:   c,
		next: next,
	}
}

// NewConfSettingStore creates an adapter using static config overwrite. Only string type value is supported.
func NewConfSettingStore(c conf.ConfigProvider, next SettingStoreAdapter) SettingStoreAdapter {
	return &staticSettingStore{
		settings: c.OptionOverwrite(),
		next:     next,
	}
}

// NewDbDefaultStore creates an adapter that always returns default setting value defined in inventory.DefaultSettings.
// Only string type value is supported.
func NewDbDefaultStore(next SettingStoreAdapter) SettingStoreAdapter {
	return &staticSettingStore{
		settings: lo.MapValues(inventory.DefaultSettings, func(v string, _ string) any {
			return v
		}),
		next: next,
	}
}

// NewEnvOverwriteStore creates an adapter that always returns overrided setting value defined in environment variables.
func NewEnvOverrideStore(next SettingStoreAdapter, l logging.Logger) SettingStoreAdapter {
	allEnv := os.Environ()
	defaults := make(map[string]any)
	for _, env := range allEnv {
		kv := strings.SplitN(env, "=", 2)
		if strings.HasPrefix(kv[0], EnvSettingOverwritePrefix) {
			key := strings.TrimPrefix(kv[0], EnvSettingOverwritePrefix)
			defaults[key] = kv[1]
			l.Info("Override setting %q with value %q from environment", key, kv[1])
		}
	}

	return &staticSettingStore{
		settings: defaults,
		next:     next,
	}
}

type dbSettingStore struct {
	c    inventory.SettingClient
	next SettingStoreAdapter
}

func (s *dbSettingStore) Get(ctx context.Context, name string, defaultVal any) any {
	if val, err := s.c.Get(ctx, name); err == nil {
		return val
	}

	if s.next != nil {
		return s.next.Get(ctx, name, defaultVal)
	}

	return defaultVal
}

type kvSettingStore struct {
	kv   cache.Driver
	next SettingStoreAdapter
}

func (s *kvSettingStore) Get(ctx context.Context, name string, defaultVal any) any {
	if val, exist := s.kv.Get(KvSettingPrefix + name); exist {
		return val
	}

	if s.next != nil {
		nextVal := s.next.Get(ctx, name, defaultVal)
		// cache setting value
		s.kv.Set(KvSettingPrefix+name, nextVal, 0)
		return nextVal
	}

	return defaultVal
}

type staticSettingStore struct {
	settings map[string]any
	next     SettingStoreAdapter
}

func (s *staticSettingStore) Get(ctx context.Context, name string, defaultVal any) any {
	if val, ok := s.settings[name]; ok {
		return val
	}

	if s.next != nil {
		return s.next.Get(ctx, name, defaultVal)
	}

	return defaultVal
}
