package config

import (
	"context"
	"errors"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"slices"
	"strings"

	"github.com/google/uuid"
	"github.com/gookit/goutil/dump"
	"github.com/joho/godotenv"
	"github.com/spf13/viper"
)

// var routeRulesNames = []string{"Host", "Path", "PathPrefix"}
const DEFAULT_CONFIG_TYPE = "yaml"

var DEFAULT_CONFIG_PATH string
var DEFAULT_CONFIG_NAME string

var Data *Config

func init() {
	err := godotenv.Load(".env")
	if err != nil {
		log.Fatalf("Error loading .env file: %v", err)
	}
	DEFAULT_CONFIG_PATH = os.Getenv("CONFIG_PATH")
	DEFAULT_CONFIG_NAME = os.Getenv("CONFIG_NAME")

	Data = ConfigBuild()
}

type Config struct {
	Routers     Routers
	Services    Services
	EntryPoints EntryPoints
	TLS         TLS
	AuthMap     AuthMap
	Health      *Health
	Logs        *Logs
	Context     context.Context
}
type Logs struct {
	ConfigBuild []string
}
type Health struct {
	ConfigHealth *ConfigHealth
}
type ConfigHealth struct {
	Routers     bool
	Services    bool
	EntryPoints bool
	AuthBuild   bool
}
type ConfigLog map[string][]string

type AuthMap map[string]*Auth
type Auth struct {
	Domain        string
	SessionSecret string
	Paths         *Paths
	OpenID        *OpenID
}
type OpenID struct {
	EndPoints    EndPoints
	Realm        string
	ClientID     string
	ClientSecret string
}
type EndPoints struct {
	Issuer                string
	AuthURL               string
	TokenURL              string
	UserURL               string
	LogoutURL             string
	Config                string
	RedirectURI           string
	PostLogoutRedirectUri string
	JwksURI               string
}
type Paths struct {
	Prefix     string
	Login      string
	Logout     string
	Callback   string
	PostLogout string
}
type EntryPoints map[string]*EntryPoint
type EntryPoint struct {
	Address string
	TLS     *EntryPointTLS
}
type EntryPointTLS struct {
	Enabled bool
}
type TLS struct {
	CertProviders CertProviders
}
type CertProviders map[string]*certProvider
type certProvider struct {
	Cert string
	Key  string
}
type Routers map[string]*Router

type Router struct {
	IsAuthRouter bool
	StripPrefix  bool
	Priority     int
	Name         string
	Service      string
	EntryPoint   string
	Routes       Routes
	Auth         RouterAuth
}
type RouterAuth struct {
	JWT      *JWT
	Enabled  bool
	Provider string
}
type JWT struct {
	AllowLists map[string][]string
}
type Routes map[string]*Route

type Route struct {
	ID     string
	Router string
	Rule   map[string]string
}

type Services map[string]string

func (c *Config) InitContext(ctx context.Context) {
	c.Context = ctx
}
func (e EntryPoints) ForEach(fn func(name string, data *EntryPoint)) {
	for name, data := range e {
		fn(name, data)
	}
}
func (r Routers) ForEach(fn func(name string, data *Router) string) {

	for name, data := range r {
		msg := fn(name, data)
		if msg == "break" {
			break
		}
		if msg == "continue" {
			continue
		}
	}
}
func (r Routers) ForEachByPriority(fn func(name string, data *Router)) {
	slc := r.getSlicesByPriority()
	for _, n := range slc {
		d := r[n]
		fn(n, d)
	}
}
func (r Routers) getSlicesByPriority() []string {

	routersSlice := []string{}
	r.ForEach(func(routerName string, routerData *Router) string {
		routersSlice = append(routersSlice, routerName)
		return ""
	})
	slices.SortFunc(routersSlice, func(a, b string) int {
		if r[a].Priority == r[b].Priority {
			return 0
		}
		if r[a].Priority > r[b].Priority {
			return -1
		}
		if r[a].Priority < r[b].Priority {
			return 1
		}
		return r[a].Priority - r[b].Priority
	})
	return routersSlice

}
func (r *Router) GetRouteByID(routeID string) *Route {
	return r.Routes[routeID]
}
func (r Routes) ForEach(fn func(routeID string, v *Route) string) {

	for routeID, v := range r {
		msg := fn(routeID, v)
		if msg == "break" {
			break
		}
		if msg == "continue" {
			continue
		}
	}
}
func (r Route) ForEachRule(fn func(routeID string, ruleName string, ruleValue string) string) {
	for k, v := range r.Rule {
		msg := fn(r.ID, k, v)
		if msg == "break" {
			break
		}
	}
}
func BuildAuthPaths(authData any) *Paths {
	pathsData := getMap(authData, "paths")
	return &Paths{
		Prefix:     getMapString(pathsData, "prefix"),
		Login:      getMapString(pathsData, "login"),
		Logout:     getMapString(pathsData, "logout"),
		Callback:   getMapString(pathsData, "callback"),
		PostLogout: getMapString(pathsData, "postlogout"),
	}
}
func InitConfigStruct() *Config {
	config := &Config{
		Routers:     Routers{},
		Services:    Services{},
		EntryPoints: EntryPoints{},
		TLS: TLS{
			CertProviders: CertProviders{},
		},
		AuthMap: AuthMap{},
		Health: &Health{
			ConfigHealth: &ConfigHealth{
				Routers:     false,
				Services:    false,
				EntryPoints: false,
				AuthBuild:   false,
			},
		},
		Logs: &Logs{
			ConfigBuild: []string{},
		},
	}

	return config
}
func ConfigBuild() *Config {
	vc := NewViperConfig()
	c := InitConfigStruct()
	// //Routers
	// //Services
	BuildRoutersConfig(vc, c, "routers")
	BuildServicesConfig(vc, c, "services")
	BuildCertProvidersConfig(vc, c, "tls.certproviders")
	BuildEntryPointsConfig(vc, c, "entrypoints")
	BuildAuthConfig(vc, c, "auth")
	// if !BuildServicesConfig(viperConfig, config, "services") {
	// 	err = errors.Join(errors.New("build services config error ="))
	// }
	// if !BuildRoutersConfig(viperConfig, config, "routers") {
	// 	err = errors.Join(errors.New("build routers config error"))
	// }

	// // //Entrypoints
	// if !BuildEntryPointsConfig(viperConfig, config, "entryPoints") {
	// 	err = errors.Join(errors.New("build entry points config error"))
	// }
	// // //TLS Certificate Providers
	// if !BuildCertProvidersConfig(viperConfig, config, "tls.certproviders") {
	// 	err = errors.Join(errors.New("build cert providers config error"))
	// }
	// if !BuildAuthConfig(viperConfig, config, "auth") {
	// 	// err = errors.Join(errors.New("build auth config error"))
	// }

	return c
}

func BuildCertProvidersConfig(viperConfig *viper.Viper, config *Config, key string) bool {
	if viperConfig.Sub(key) == nil {
		config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
		return false
	} else {
		certProviders := viperConfig.Sub(key).AllSettings()

		// config.TLS.CertProviders = make(CertProviders)
		for k, v := range certProviders {
			v := v.(map[string]any)
			cert := v["cert"].(string)
			key := v["key"].(string)
			config.TLS.CertProviders[k] = &certProvider{
				Cert: cert,
				Key:  key,
			}
		}
		return true
	}
}
func BuildServicesConfig(viperConfig *viper.Viper, config *Config, key string) bool {
	if viperConfig.Sub(key) == nil {

		config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
		dump.P("Missing \"" + key + "\" in Config")
		return false
	} else {
		config.Services = make(Services)
		services := viperConfig.Sub(key).AllSettings()
		for k, v := range services {
			config.Services[k] = v.(string)
		}
		return true
	}
}
func BuildEntryPointsConfig(viperConfig *viper.Viper, config *Config, key string) bool {
	if viperConfig.Sub(key) == nil {
		config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
		dump.P("Missing \"" + key + "\" in Config")

		return false
	} else {
		entryPoints := viperConfig.Sub(key).AllSettings()
		for k, v := range entryPoints {

			entryPoint := &EntryPoint{
				Address: "",
				TLS: &EntryPointTLS{
					Enabled: false,
				},
			}
			v := v.(map[string]any)

			if _, ok := v["address"]; ok {
				entryPoint.Address = v["address"].(string)
			}
			if _, ok := v["tls"]; ok {
				tls := v["tls"].(map[string]any)
				enabled := tls["enabled"].(bool)
				entryPoint.TLS = &EntryPointTLS{
					Enabled: enabled,
				}
			}

			config.EntryPoints[k] = entryPoint
		}
		return true
	}
}
func getMap(mp any, key string) map[string]any {

	if val, ok := mp.(map[string]any)[key]; ok {
		return val.(map[string]any)
	} else {
		return val.(map[string]any)
	}
}
func getMapString(mp any, key string) string {

	if val, ok := mp.(map[string]any)[key]; ok {
		return val.(string)
	} else {
		return ""
	}
}
func markWrapper(str string, mark []string) string {
	return mark[0] + str + mark[1]
}
func BuildAuthConfig(viperConfig *viper.Viper, config *Config, key string) bool {
	if viperConfig.Sub(key) == nil {
		config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
		dump.P("Missing \"" + key + "\" in Config")

		return false
	} else {
		authData := viperConfig.Sub(key).AllSettings()

		mark := []string{"<{{", "}}>"}

		realmUrlMark := markWrapper("realm", mark)
		authRootUrlMark := markWrapper("auth_root_url", mark)
		targetRootUrlMark := markWrapper("target_root_url", mark)
		authLocalRootUrlMark := markWrapper("auth_local_root_url", mark)

		for authName, v := range authData {
			auth := &Auth{}
			openid := &OpenID{}

			// dump.P(auth)
			auth.Paths = BuildAuthPaths(v)
			// endPoints := &EndPoints{}

			authRootURL := getMapString(v, "auth_root_url")
			targetRootURL := getMapString(v, "target_root_url")
			authLocalRootURL := getMapString(v, "auth_local_root_url")

			openidData := getMap(v, "openid")
			endPointsData := getMap(openidData, "end_points")

			// for k, v := range openidData {
			// 	if k != "end_points" {
			// 		v := v.(string)
			// 		field := helper.CapitalizeFirstLetter(k)
			// 		field = helper.CapitalizeAfterCharMulti(field, '_')
			// 		field = strings.ReplaceAll(field, "_", "")
			// 		if strings.HasSuffix(field, "Id") {
			// 			field, _ = strings.CutSuffix(field, "Id")
			// 			field += "ID"
			// 		}
			// 		helper.InsertStringValueIntoField(openid, field, v)
			// 		dump.P(field, v)

			// 	}

			// }
			openid.Realm = openidData["realm"].(string)
			openid.ClientID = openidData["client_id"].(string)
			openid.ClientSecret = openidData["client_secret"].(string)

			endPointsMapStringString := make(map[string]string)
			for k, v := range endPointsData {
				val := v.(string)
				if strings.Contains(val, realmUrlMark) {
					val = strings.ReplaceAll(val, realmUrlMark, openid.Realm)
				}
				if strings.Contains(val, authRootUrlMark) {
					val = strings.ReplaceAll(val, authRootUrlMark, authRootURL)
				}
				if strings.Contains(val, targetRootUrlMark) {
					val = strings.ReplaceAll(val, targetRootUrlMark, targetRootURL)
				}
				if strings.Contains(val, authLocalRootUrlMark) {
					val = strings.ReplaceAll(val, authLocalRootUrlMark, authLocalRootURL)
				}
				endPointsMapStringString[k] = val
			}

			if epData, ok := endPointsMapStringString["issuer"]; ok {
				openid.EndPoints.Issuer = epData
			}
			if epData, ok := endPointsMapStringString["redirect_uri"]; ok {

				openid.EndPoints.RedirectURI = epData
			}
			if epData, ok := endPointsMapStringString["post_logout_redirect_uri"]; ok {
				openid.EndPoints.PostLogoutRedirectUri = epData
			}
			if epData, ok := endPointsMapStringString["config"]; ok {
				openid.EndPoints.Config = epData
			}
			if epData, ok := endPointsMapStringString["authurl"]; ok {
				openid.EndPoints.AuthURL = epData
			}
			if epData, ok := endPointsMapStringString["tokenurl"]; ok {
				openid.EndPoints.TokenURL = epData
			}
			if epData, ok := endPointsMapStringString["userurl"]; ok {
				openid.EndPoints.UserURL = epData
			}
			if epData, ok := endPointsMapStringString["logouturl"]; ok {
				openid.EndPoints.LogoutURL = epData
			}
			if epData, ok := endPointsMapStringString["jwksuri"]; ok {
				openid.EndPoints.JwksURI = epData
			}

			auth.OpenID = openid
			config.AuthMap[authName] = auth

		}

		return true
	}

}
func splitStatementByRegex(str string, regx string) []string {

	input := str
	re := regexp.MustCompile(regx)
	matches := re.FindAllStringSubmatch(input, -1)
	if len(matches) == 0 {
		return []string{str} // Return original if no backticks found
	}

	result := []string{}
	lastIndex := 0

	for _, match := range matches {

		startIndex := strings.Index(input, match[0])

		if startIndex > lastIndex {
			str := input[lastIndex:startIndex]
			str, _ = strings.CutPrefix(str, ".")
			result = append(result, str)
		}
		str := match[1]
		str, _ = strings.CutPrefix(str, ".")
		result = append(result, str) // Append the content within backticks
		lastIndex = startIndex + len(match[0])
	}

	if lastIndex < len(input) {
		str := input[lastIndex:]
		str, _ = strings.CutPrefix(str, ".")
		result = append(result, str)
	}

	return result
}
func BuildRoutersConfig(viperConfig *viper.Viper, config *Config, key string) bool {
	if viperConfig.Sub(key) == nil {
		config.Logs.ConfigBuild = append(config.Logs.ConfigBuild, "Missing \""+key+"\" in Config")
		dump.P("Missing \"" + key + "\" in Config")
		return false
	} else {
		routers := viperConfig.Sub(key).AllSettings()
		for routerName, d := range routers {
			data := d.(map[string]any)

			// var (
			// 	routes      []any
			// 	service     string
			// 	entryPoint  string
			// 	priority    int
			// 	stripPrefix bool
			// )
			if _, ok := data["routes"]; !ok {
				continue
			}
			if _, ok := data["service"]; !ok {
				continue
			}
			if _, ok := data["entrypoint"]; !ok {
				continue
			}
			if _, ok := data["priority"]; !ok {
				data["priority"] = 1000
			}
			if _, ok := data["routes"]; !ok {
				continue
			}
			if _, ok := data["stripprefix"]; !ok {
				data["stripprefix"] = false
			}
			routes := data["routes"].([]any)
			service := data["service"].(string)
			entryPoint := data["entrypoint"].(string)
			priority := data["priority"].(int)
			stripPrefix := data["stripprefix"].(bool)
			isAuthRouter := false
			if _, ok := data["is_auth_router"]; ok {
				isAuthRouter = data["is_auth_router"].(bool)
			}

			//AUTH
			authEnabled := false
			provider := ""
			jwt := &JWT{
				AllowLists: map[string][]string{},
			}

			if d, ok := data["auth"]; ok {

				if _, ok := d.(map[string]any)["enabled"]; ok {
					authEnabled = d.(map[string]any)["enabled"].(bool)
				}

				provider = d.(map[string]any)["provider"].(string)
				if jwtData, ok := d.(map[string]any)["jwt"]; ok {
					if allow, ok := jwtData.(map[string]any)["allow"]; ok {
						a := allow.(map[string]any)
						for f, s := range a {
							field := f
							slice := []string{}
							for _, v := range s.([]any) {
								slice = append(slice, v.(string))
							}
							jwt.AllowLists[field] = slice
							dump.P(slice)
						}

					}
				}
				// if _, ok := d.(map[string]any)["emails"]; ok {
				// 	for _, e := range d.(map[string]any)["emails"].([]any) {
				// 		email := e.(string)
				// 		emails = append(emails, email)
				// 	}
				// }
			}

			router := &Router{
				Name:         routerName,
				IsAuthRouter: isAuthRouter,
				Priority:     priority,
				Service:      service,
				EntryPoint:   entryPoint,
				StripPrefix:  stripPrefix,
				Routes:       Routes{},
				Auth: RouterAuth{
					Enabled:  authEnabled,
					Provider: provider,
					JWT:      jwt,
					// Emails:   emails,
				},
			}

			for _, v := range routes {
				routeID := uuid.New().String()
				route := &Route{
					ID:     routeID,
					Router: routerName,
					Rule:   map[string]string{},
				}
				rawRoute := v.(string)
				route.Rule["raw"] = rawRoute
				routeStatmentsLogicSplit := strings.Split(rawRoute, "&&")
				for _, logicPart := range routeStatmentsLogicSplit {
					logicPart = strings.TrimSpace(logicPart)
					routeStatmentsSlice := splitStatementByRegex(logicPart, "\\(`(.*?)`\\)")
					for i, v := range routeStatmentsSlice {
						if i&1 == 0 {
							if i+1 < len(routeStatmentsSlice) {
								route.Rule[v] = routeStatmentsSlice[i+1]
							}
						}
					}
				}
				// routeStatmentsSlice := splitStatementByRegex(rawRoute, "\\(`(.*?)`\\)")

				// for i, v := range routeStatmentsSlice {
				// 	if i&1 == 0 {
				// 		if i+1 < len(routeStatmentsSlice) {
				// 			route.Rule[v] = routeStatmentsSlice[i+1]
				// 		}
				// 	}
				// }

				router.Routes[routeID] = route

				// router.Routes = append(router.Routes, route)
			}
			config.Routers[routerName] = router

		}
		return true

	}
}

// func NewViperConfig() *viper.Viper {
// 	vc := viper.New()
// 	vc.AddConfigPath(DEFAULT_CONFIG_PATH)
// 	vc.SetConfigType(DEFAULT_CONFIG_TYPE)
// 	vc.SetConfigName(DEFAULT_CONFIG_NAME)
// 	vc.ReadInConfig()

// 	fileNames, err := getFileNames(DEFAULT_CONFIG_PATH, DEFAULT_CONFIG_NAME)

// 	if err != nil {
// 		dump.Println("No extra config files" + err.Error())
// 	} else {
// 		for _, fileName := range fileNames {
// 			vc.SetConfigName(fileName)
// 			vc.MergeInConfig()
// 		}
// 	}

//		return vc
//	}
func NewViperConfig() *viper.Viper {
	vc := viper.New()
	vc.AddConfigPath(DEFAULT_CONFIG_PATH)
	vc.SetConfigType(DEFAULT_CONFIG_TYPE)
	vc.SetConfigName(DEFAULT_CONFIG_NAME)
	err := vc.ReadInConfig()
	if err != nil {
		dump.P(err.Error())
	}
	// vc1 := viper.New()
	// vc1.AddConfigPath(DEFAULT_CONFIG_PATH)
	// vc1.SetConfigType(DEFAULT_CONFIG_TYPE)
	// vc1.SetConfigName("auth")
	// vc1.ReadInConfig()

	// vc.MergeConfigMap(vc1.AllSettings())
	// vc.MergeConfigMap(vc1.AllSettings())

	fileNames, err := getFileNames(DEFAULT_CONFIG_PATH, DEFAULT_CONFIG_NAME)

	if err != nil {
		dump.Println("No extra config files" + err.Error())
	} else {
		for _, fileName := range fileNames {
			vc.SetConfigName(fileName)
			vc.MergeInConfig()
		}
	}

	return vc
}
func getYamlFileNameExcExt(fileName string, exts ...string) (string, error) {
	var fileExt string
	for _, ext := range exts {
		if strings.HasSuffix(fileName, ext) {
			fileExt = ext
			break
		}
	}

	if name, ok := strings.CutSuffix(fileName, fileExt); ok && fileExt != "" {
		return name, nil
	} else {
		return "", errors.New("error: no file extension found ")
	}
}
func getFileNames(folderPath string, skipFileName string) ([]string, error) {
	var fileNames []string

	err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !info.IsDir() { // Only add files, not directories
			filename := info.Name()
			filesize := info.Size()
			filenameNoExt, _ := getYamlFileNameExcExt(filename, ".yaml", ".yml")
			if skipFileName == filenameNoExt || filesize == 0 {
				return nil
			}
			fileNames = append(fileNames, filenameNoExt)
		}
		return nil
	})

	if err != nil {
		return nil, err
	}

	return fileNames, nil
}
