package config

import (
	"fmt"
	"github.com/UpmeshLTD/urus-aio/infrastructure/config/registry"
	"strings"
	"sync"



	"github.com/spf13/viper"
)

// Provider represents the configuration provider.
//go:generate mockery --name=Provider --case=snake
type Provider interface {
	// Get returns a configuration with given name.
	Get(name string) interface{}
}

// ProviderOpt is an option on a given Provider.
type ProviderOpt func(impl *providerImpl)

// ViperInitializer is a function that initializes a given Viper object.
type ViperInitializer func(v *viper.Viper)

// providerImpl implements Provider interface.
type providerImpl struct {
	viper            *viper.Viper
	viperInitializer ViperInitializer

	once *sync.Once
}

// NewProvider creates a new instance of configuration provider.
func NewProvider(opts ...ProviderOpt) Provider {
	provider := &providerImpl{
		viper: viper.New(),
		viperInitializer: func(v *viper.Viper) {
			v.SetConfigName("app-config") // name of config file (without extension)
			v.SetConfigType("yaml")
			v.AddConfigPath("$APP_CONFIG_DIR")
			v.AddConfigPath(".")
			v.AddConfigPath("$HOME")
			v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
			v.SetEnvPrefix("APP")
			v.AutomaticEnv()
		},
		once: &sync.Once{},
	}

	for _, o := range opts {
		o(provider)
	}

	return provider
}

func (impl *providerImpl) initializeOnce() {
	impl.once.Do(func() {
		registry.SetDefaultConfigs(impl.viper)
		impl.viperInitializer(impl.viper)
		_ = impl.viper.ReadInConfig()
	})
}

func (impl *providerImpl) Get(name string) interface{} {
	impl.initializeOnce()

	config := registry.GetConfig(name)
	if config == nil {
		panic(fmt.Sprintf("cannot find configuration with name %s", name))
	}

	return config.Get(impl.viper)
}

// WithViperInitializer returns an option that allows setting of viper initializer in the provider.
func WithViperInitializer(viperInitializer ViperInitializer) ProviderOpt {
	return func(impl *providerImpl) {
		impl.viperInitializer = viperInitializer
	}
}
