package config

import (
	"encoding/json"
	"io"
	"log"
	"net/http"
	"strings"

	"github.com/spf13/viper"
	"zeevdiukman.com/zprox/pkg/helper"
)

const Viper_File_Name string = "config"
const Viper_File_Type string = "yaml"
const Viper_File_Path string = "."

var (
	configData *Config
)

func init() {
	h := helper.New()
	h.Screen.Clear()
	configData = createConfig()
}

func Wrapper(fn func(c *Config)) {
	fn(configData)
}
func Get() *Config {

	return configData
}
func Get2() *Config {
	return configData
}
func createConfig() *Config {

	c := &Config{}
	c.initViper()
	c.setViperOptions(Viper_File_Name, Viper_File_Type, Viper_File_Path)
	c.viperReadYaml()
	c.viperUnmarshalYaml()
	c.initDomainToProxyNameMap()
	c.initDomainToProxyAuthName()
	c.initDomainToCertName()
	c.initOpenIDEndPoints()
	return c
}

func (c *Config) initViper() {
	c.Viper = viper.New()
}

func (c *Config) setViperOptions(fileName string, fileExtention string, filePath string) {
	c.Viper.SetConfigName(fileName)
	c.Viper.SetConfigType(fileExtention)
	c.Viper.AddConfigPath(filePath)
}

func (c *Config) viperReadYaml() {
	err := c.Viper.ReadInConfig()
	if err != nil {
		log.Fatalf("Error reading config file, %s", err)
	}
}
func (c *Config) viperUnmarshalYaml() {
	err := c.Viper.Unmarshal(&c)
	if err != nil {
		log.Fatalf("Unable to decode into struct, %v", err)
	}
}

func (eps *EntryPoints) ForEach(fn func(epName string, epConfig *EntryPoint)) {
	for epName, epConfig := range *eps {
		fn(epName, &epConfig)
	}
}

func (rp *ReverseProxies) ForEach(fn func(rpName string, rpConfig *ReverseProxy)) {
	for rpName, rpData := range *rp {
		fn(rpName, &rpData)
	}
}

func (rp *ReverseProxies) ForEachReturn(fn func(rpName string, rpConfig *ReverseProxy) (res ForEachReturn)) *ForEachReturn {
	res := ForEachReturn{}
	for rpName, rpData := range *rp {
		res = append(fn(rpName, &rpData), res)
	}
	return &res
}

//	func (rp AuthMap) ForEach(fn func(authName string, authConfig Auth)) {
//		for authName, authConfig := range rp {
//			fn(authName, authConfig)
//		}
//	}
func (c *Config) fetchEndPointsByAuthName(authName string) map[string]any {
	configURL := c.KeycloakWellknownURL(authName)
	resp := helper.IsFetchOK[EndPoints](configURL, "", http.Get)
	respByes, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Println(err.Error())
	}
	data := map[string]any{}
	json.Unmarshal(respByes, &data)
	return data
}

func (c *Config) initOpenIDEndPoints() {
	for authName := range c.AuthMap {
		data := c.fetchEndPointsByAuthName(authName)

		c.AuthMap[authName].OpenID.EndPoints = &EndPoints{
			Issuer:        data["issuer"].(string),
			Auth:          data["authorization_endpoint"].(string),
			Introspection: data["introspection_endpoint"].(string),
			Token:         data["token_endpoint"].(string),
			UserInfo:      data["userinfo_endpoint"].(string),
			Logout:        data["end_session_endpoint"].(string),
			JwksUri:       data["jwks_uri"].(string),
		}

	}

}
func (c *Config) KeycloakWellknownURL(authName string) string {

	if _, ok := c.AuthMap[authName]; !ok {
		return ""
	}
	hostUrl := c.AuthMap[authName].OpenID.Host
	realm := c.AuthMap[authName].OpenID.Realm
	configPath := c.AuthMap[authName].OpenID.ConfigPath
	u := hostUrl
	u += strings.ReplaceAll(configPath, "<{{realm}}>", realm)
	return u
}

func (c *Config) GenerateDynamicRedirectUri(proxyName string, authName string) string {
	if _, ok := c.AuthMap[authName]; !ok {
		return ""
	}

	proto := "http"

	domain := c.ReverseProxies[proxyName].Domain
	tls := c.ReverseProxies[proxyName].TLS.Enabled
	if tls {
		proto = "https"
	}
	redirectURI := c.AuthMap[authName].OpenID.RedirectURI
	u := redirectURI
	u = strings.ReplaceAll(redirectURI, "<{{dynamic}}>", proto+"://"+domain)

	return u
}
func (c *Config) initDomainToProxyNameMap() {
	mp := make(map[string]string)
	c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) {
		mp[rpConfig.Domain] = rpName
	})
	c.DataMaps.DomainToProxy = mp
}
func (c *Config) initDomainToProxyAuthName() {

	authMap := map[string]int{}
	for authName := range c.AuthMap {
		authMap[authName] = 1
	}

	mp := make(map[string]string)
	c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) {
		if v, ok := authMap[rpConfig.Auth]; ok && v == 1 {
			mp[rpConfig.Domain] = rpConfig.Auth

		}

	})
	c.DataMaps.DomainToAuth = mp

}
func (c *Config) initDomainToCertName() {
	crtMap := map[string]int{}
	for crtName := range c.TLS.Certs {
		crtMap[crtName] = 1
	}
	mp := make(map[string]string)
	c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) {
		if v, ok := crtMap[rpConfig.TLS.Certs]; ok && v == 1 {
			mp[rpConfig.Domain] = rpConfig.TLS.Certs
		}
		mp[rpConfig.Domain] = rpConfig.TLS.Certs
	})
	c.DataMaps.DomainToCert = mp
}
func (c *Config) GetAuthNameByDomain(domain string) string {
	return c.DataMaps.DomainToAuth[domain]
}

func (c *Config) GetProxyNameByDomain(domain string) string {
	return c.DataMaps.DomainToProxy[domain]
}
func (c *Config) GetCertNameByDomain(domain string) string {
	return c.DataMaps.DomainToCert[domain]
}
func (c *Config) GetCertsPairByDomain(domain string) (string, string) {
	var crt string
	var key string
	certName := c.DataMaps.DomainToCert[domain]
	crt = c.TLS.Certs[certName].Cert
	key = c.TLS.Certs[certName].Key
	if !(certName != "" && crt == "" && key != "") {
		certName = "default"
		crt = c.TLS.Certs[certName].Cert
		key = c.TLS.Certs[certName].Key
	}

	return crt, key
}

func (c *Config) GetProxiesSliceByAuthName(authName string) []string {
	slc := &[]string{}
	c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) {
		isOK := true
		if authName != rpConfig.Auth {
			isOK = false
		}
		if _, ok := c.AuthMap[authName]; !ok {
			isOK = false
		}

		if isOK {
			helper.AppendToPointer(slc, rpName)
		}
	})
	return *slc
}
func (c *Config) GetAuthNameByProxyName(proxyName string) string {
	return c.ReverseProxies[proxyName].Auth
}
