package staticprovider

import (
	"context"
	"os"
	"path/filepath"
	"sync"

	"git.optclblast.xyz/draincloud/draincloud-core/internal/config"
	"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

var _ config.Provider = new(staticProvider)

type StaticProvider interface {
	config.Provider
}

type staticProvider struct {
	m         sync.RWMutex
	rawValues map[string]any
}

func (p *staticProvider) GetValue(ctx context.Context, key config.Key) config.Value {
	p.m.RLock()
	defer p.m.RUnlock()
	rawVal, ok := p.rawValues[string(key)]
	if !ok {
		return config.EmptyValue{}
	}

	switch val := rawVal.(type) {
	case int:
		return config.IntValue{
			Val: val,
		}
	case string:
		return config.StringValue{
			Val: val,
		}
	case float64:
		return config.FloatValue{
			Val: val,
		}
	default:
		return config.EmptyValue{}
	}
}

type newStaticProviderOptions struct {
	configName     string
	configDirPath  string
	configFileType string
}

func mustDefaultNewStaticProviderOptions(ctx context.Context) *newStaticProviderOptions {
	ex, err := os.Executable()
	if err != nil {
		logger.Fatal(ctx, "failed to get executable location", logger.Err(err))
	}

	exPath := filepath.Dir(ex)

	return &newStaticProviderOptions{
		configName:     "config",
		configDirPath:  exPath,
		configFileType: "yaml",
	}
}

type NewStaticProviderOption func(o *newStaticProviderOptions)

func WithConfigDir(path string) NewStaticProviderOption {
	return func(o *newStaticProviderOptions) {
		o.configDirPath = path
	}
}

func WithConfigType(t string) NewStaticProviderOption {
	return func(o *newStaticProviderOptions) {
		o.configFileType = t
	}
}

func WithConfigName(name string) NewStaticProviderOption {
	return func(o *newStaticProviderOptions) {
		o.configName = name
	}
}

func NewStaticProvider(
	ctx context.Context,
	opts ...NewStaticProviderOption,
) (*staticProvider, error) {
	o := mustDefaultNewStaticProviderOptions(ctx)
	for _, opt := range opts {
		opt(o)
	}

	// TODO check if ile exists

	provider := &staticProvider{
		rawValues: make(map[string]any),
	}

	viper.SetConfigName(o.configName)
	viper.SetConfigType(o.configFileType)
	viper.AddConfigPath(o.configDirPath)

	viper.WatchConfig()

	viper.OnConfigChange(func(_ fsnotify.Event) {
		provider.m.Lock()
		defer provider.m.Unlock()
		provider.rawValues = viper.AllSettings()
	})

	provider.rawValues = viper.AllSettings()

	return provider, nil
}
