package settings

import (
	"github.com/caarlos0/env/v11"
	"github.com/spf13/cast"
	"gopkg.in/ini.v1"
	"log"
	"os"
	"reflect"
	"strings"
	"time"
)

var (
	buildTime    string
	LastModified string

	Conf      *ini.File
	ConfPath  string
	EnvPrefix = "NGINX_UI_"
)

var sections = map[string]interface{}{
	"server":    &ServerSettings,
	"nginx":     &NginxSettings,
	"openai":    &OpenAISettings,
	"casdoor":   &CasdoorSettings,
	"logrotate": &LogrotateSettings,
	"cluster":   &ClusterSettings,
	"auth":      &AuthSettings,
	"crypto":    &CryptoSettings,
	"webauthn":  &WebAuthnSettings,
}

func init() {
	t := time.Unix(cast.ToInt64(buildTime), 0)
	LastModified = strings.ReplaceAll(t.Format(time.RFC1123), "UTC", "GMT")
}

func Init(confPath string) {
	ConfPath = confPath
	Setup()
}

func load() (err error) {
	Conf, err = ini.LoadSources(ini.LoadOptions{
		Loose:        true,
		AllowShadows: true,
	}, ConfPath)

	return
}

func Setup() {
	err := load()

	if err != nil {
		log.Fatalf("settings.Setup: %v\n", err)
	}

	MapTo()

	parseEnv(&ServerSettings, "SERVER_")
	parseEnv(&NginxSettings, "NGINX_")
	parseEnv(&OpenAISettings, "OPENAI_")
	parseEnv(&CasdoorSettings, "CASDOOR_")
	parseEnv(&LogrotateSettings, "LOGROTATE_")
	parseEnv(&AuthSettings, "AUTH_")
	parseEnv(&CryptoSettings, "CRYPTO_")
	parseEnv(&WebAuthnSettings, "WEBAUTHN_")

	// if in official docker, set the restart cmd of nginx to "nginx -s stop",
	// then the supervisor of s6-overlay will start the nginx again.
	if cast.ToBool(os.Getenv("NGINX_UI_OFFICIAL_DOCKER")) {
		NginxSettings.RestartCmd = "nginx -s stop"
	}

	if AuthSettings.BanThresholdMinutes <= 0 {
		AuthSettings.BanThresholdMinutes = 10
	}

	if AuthSettings.MaxAttempts <= 0 {
		AuthSettings.MaxAttempts = 10
	}

	err = Save()
	if err != nil {
		log.Fatalf("settings.Setup: %v\n", err)
	}
}

func MapTo() {
	for k, v := range sections {
		err := mapTo(k, v)

		if err != nil {
			log.Fatalf("Cfg.MapTo %s err: %v", k, err)
		}
	}
}

func Save() (err error) {
	for k, v := range sections {
		reflectFrom(k, v)
	}

	// fix unable to save empty slice
	if len(ServerSettings.RecursiveNameservers) == 0 {
		Conf.Section("server").Key("RecursiveNameservers").SetValue("")
	}

	err = Conf.SaveTo(ConfPath)
	if err != nil {
		return
	}
	return
}

func ProtectedFill(targetSettings interface{}, newSettings interface{}) {
	s := reflect.TypeOf(targetSettings).Elem()
	vt := reflect.ValueOf(targetSettings).Elem()
	vn := reflect.ValueOf(newSettings).Elem()

	// copy the values from new to target settings if it is not protected
	for i := 0; i < s.NumField(); i++ {
		if s.Field(i).Tag.Get("protected") != "true" {
			vt.Field(i).Set(vn.Field(i))
		}
	}
}

func mapTo(section string, v interface{}) error {
	return Conf.Section(section).MapTo(v)
}

func reflectFrom(section string, v interface{}) {
	err := Conf.Section(section).ReflectFrom(v)
	if err != nil {
		log.Fatalf("Cfg.ReflectFrom %s err: %v", section, err)
	}
}

func parseEnv(ptr interface{}, prefix string) {
	err := env.ParseWithOptions(ptr, env.Options{
		Prefix:                EnvPrefix + prefix,
		UseFieldNameByDefault: true,
	})

	if err != nil {
		log.Fatalf("settings.parseEnv: %v\n", err)
	}
}
