package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/fs"
	"log/slog"
	"os"
	"reflect"
	"strings"

	"github.com/tinyauthapp/tinyauth/internal/model"
)

type EnvEntry struct {
	Name        string
	Description string
	Value       any
}

func generateExampleEnv() {
	cfg := model.NewDefaultConfiguration(model.RuntimeEnvUnknown)
	entries := make([]EnvEntry, 0)

	root := reflect.TypeOf(cfg).Elem()
	rootValue := reflect.ValueOf(cfg).Elem()
	rootPath := "TINYAUTH_"

	walkAndBuild(root, rootValue, rootPath, &entries, buildEnvEntry, buildEnvMapEntry, buildEnvChildPath)
	compiled := compileEnv(entries)

	err := os.Remove(".env.example")
	if err != nil && !errors.Is(err, fs.ErrNotExist) {
		slog.Error("failed to remove example env file", "error", err)
		os.Exit(1)
	}

	err = os.WriteFile(".env.example", compiled, 0644)
	if err != nil {
		slog.Error("failed to write example env file", "error", err)
		os.Exit(1)
	}
}

func buildEnvEntry(child reflect.StructField, childValue reflect.Value, parentPath string, entries *[]EnvEntry) {
	desc := child.Tag.Get("description")
	tag := child.Tag.Get("yaml")
	gen := child.Tag.Get("gen")

	if tag == "-" && gen != "include" {
		return
	}

	tag = strings.TrimSuffix(tag, ",omitempty")

	value := childValue.Interface()

	entry := EnvEntry{
		Name:        parentPath + strings.ToUpper(child.Name),
		Description: desc,
	}

	switch childValue.Kind() {
	case reflect.Slice:
		sl, ok := value.([]string)
		if !ok {
			slog.Error("invalid default value", "value", value)
			return
		}
		entry.Value = strings.Join(sl, ",")
	case reflect.String:
		st, ok := value.(string)
		if !ok {
			slog.Error("invalid default value", "value", value)
			return
		}
		if st != "" {
			entry.Value = fmt.Sprintf(`"%s"`, st)
		} else {
			entry.Value = ""
		}
	default:
		entry.Value = value
	}
	*entries = append(*entries, entry)
}

func buildEnvMapEntry(child reflect.StructField, parentPath string, entries *[]EnvEntry) {
	fieldType := child.Type

	if fieldType.Key().Kind() != reflect.String {
		slog.Info("unsupported map key type", "type", fieldType.Key().Kind())
		return
	}

	mapPath := parentPath + strings.ToUpper(child.Name) + "_name_"
	valueType := fieldType.Elem()

	if valueType.Kind() == reflect.Struct {
		zeroValue := reflect.New(valueType).Elem()
		walkAndBuild(valueType, zeroValue, mapPath, entries, buildEnvEntry, buildEnvMapEntry, buildEnvChildPath)
	}
}

func buildEnvChildPath(parent string, child string) string {
	return parent + strings.ToUpper(child) + "_"
}

func compileEnv(entries []EnvEntry) []byte {
	buffer := bytes.Buffer{}

	buffer.WriteString("# This file is automatically generated by gen/gen_env.go. Do not edit manually.\n\n")
	buffer.WriteString("# Tinyauth example configuration\n\n")

	previousSection := ""

	for _, entry := range entries {
		if strings.Count(entry.Name, "_") > 1 {
			section := strings.Split(strings.TrimPrefix(entry.Name, "TINYAUTH_"), "_")[0]
			if section != previousSection {
				buffer.WriteString("\n# " + strings.ToLower(section) + " config\n\n")
				previousSection = section
			}
		}
		buffer.WriteString("# ")
		buffer.WriteString(entry.Description)
		buffer.WriteString("\n")
		buffer.WriteString(entry.Name)
		buffer.WriteString("=")
		fmt.Fprintf(&buffer, "%v", entry.Value)
		buffer.WriteString("\n")
	}

	return buffer.Bytes()
}
