package config

import (
	"encoding"
	"encoding/base64"
	"fmt"
	"github.com/fatih/structs"
	"github.com/iancoleman/strcase"
	"github.com/spf13/viper"
	"go.uber.org/fx"
	"go.uber.org/zap"
	"reflect"
)

import (
	"crypto/ed25519"
)

type PrivateKey ed25519.PrivateKey

var (
	_ encoding.TextUnmarshaler = (*PrivateKey)(nil)
	_ encoding.TextMarshaler   = (*PrivateKey)(nil)
)

func (p *PrivateKey) UnmarshalText(text []byte) error {
	dec, err := base64.StdEncoding.DecodeString(string(text))
	if err != nil {
		return err
	}

	*p = dec

	return nil
}

func (p *PrivateKey) MarshalText() ([]byte, error) {
	return []byte(base64.StdEncoding.EncodeToString(*p)), nil
}

type Config struct {
	Host               string `mapstructure:"host" structs:"host"`
	Port               int    `mapstructure:"port" structs:"port"`
	GiteaUrl           string `mapstructure:"gitea_url" structs:"gitea_url"`
	DbPath             string `mapstructure:"db_path" structs:"db_path"`
	Oauth              OauthConfig
	JwtPrivateKey      PrivateKey
	Domain             string `mapstructure:"domain" structs:"domain"`
	GiteaWebHookSecret string `mapstructure:"gitea_webhook_secret" structs:"gitea_webhook_secret"`
}

type OauthConfig struct {
	Authorization string `mapstructure:"authorization" structs:"authorization"`
	Token         string `mapstructure:"token" structs:"token"`
	ClientId      string `mapstructure:"client_id" structs:"client_id"`
	ClientSecret  string `mapstructure:"client_secret" structs:"client_secret"`
	RefreshToken  string `mapstructure:"refresh_token" structs:"refresh_token"`
}

var Module = fx.Module("config",
	fx.Options(
		fx.Provide(NewConfig),
	),
)

func NewConfig(logger *zap.Logger) *Config {
	c := &Config{}

	_, sk, err := ed25519.GenerateKey(nil)

	if err != nil {
		logger.Fatal("Error generating private key", zap.Error(err))
	}

	// Set the default values
	viper.SetDefault("host", "localhost")
	viper.SetDefault("port", 8080)
	viper.SetDefault("gitea_url", "")
	viper.SetDefault("db_path", "data.db")
	viper.SetDefault("jwt_private_key", base64.StdEncoding.EncodeToString(sk))
	viper.SetDefault("oauth.client_id", "")
	viper.SetDefault("oauth.client_secret", "")

	viper.AddConfigPath(".")
	viper.AddConfigPath("/etc/gitea-github-proxy")
	viper.SetConfigName("config")
	viper.SetConfigType("yaml")
	err = viper.ReadInConfig()
	if err != nil {
		err = viper.SafeWriteConfig()
		if err != nil {
			logger.Fatal("Error writing config file", zap.Error(err))
		}
	}

	err = viper.Unmarshal(c)
	if err != nil {
		logger.Fatal("Error unmarshalling config", zap.Error(err))
	}

	/*	err = viper.UnmarshalKey("jwt_private_key", &c.JwtPrivateKey, viper.DecodeHook(mapstructure.TextUnmarshallerHookFunc()))
		if err != nil {
			logger.Fatal("Error unmarshalling jwtPrivateKey", zap.Error(err))
		}*/

	if len(c.GiteaUrl) == 0 {
		logger.Fatal("Gitea URL is required")
	}

	return c
}

func SaveConfig(cfg *Config) error {
	data := structs.New(cfg)

	for _, field := range data.Fields() {
		processFields(field, nil)
	}

	/*	if field, ok := interface{}(cfg.JwtPrivateKey).(encoding.TextMarshaler); ok {
			text, err := field.MarshalText()
			if err != nil {
				return err
			}
			viper.Set("jwt_private_key", string(text))
		}
	*/
	return viper.WriteConfig()
}

func processFields(f *structs.Field, parent *structs.Field) {
	if f.IsZero() {
		return
	}

	if f.Kind() == reflect.Struct {
		fields := f.Fields()

		if len(fields) > 0 {
			for _, field := range fields {
				processFields(field, f)
			}
			return
		}
	}

	name := ""

	if parent != nil {
		name = fmt.Sprintf("%s.%s", parent.Name(), f.Name())
	} else {
		name = f.Name()
	}

	name = strcase.ToSnakeWithIgnore(name, ".")

	viper.Set(name, f.Value())
}