package main

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

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

type ConfigOptions struct {
	Env  string
	Flag string
	YAML string
}
type MarkdownEntry struct {
	Options     ConfigOptions
	Description string
	Default     any
}

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

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

	walkAndBuild(root, rootValue, rootPath, &entries, buildMdEntry, buildMdMapEntry, buildMdChildPath)
	compiled := compileMd(entries)

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

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

func buildMdEntry(child reflect.StructField, childValue reflect.Value, parentPath string, entries *[]MarkdownEntry) {
	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()

	configOptions := ConfigOptions{
		Env:  strings.ToUpper(strings.ReplaceAll(parentPath, ".", "_")) + strings.ToUpper(child.Name),
		Flag: fmt.Sprintf("--%s%s", strings.TrimPrefix(parentPath, "tinyauth."), strings.ToLower(child.Name)),
	}

	if tag != "-" && tag != "" {
		configOptions.YAML = strings.TrimPrefix(parentPath, "tinyauth.") + tag
	}

	entry := MarkdownEntry{
		Options:     configOptions,
		Description: desc,
	}

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

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

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

	tag := child.Tag.Get("yaml")
	gen := child.Tag.Get("gen")

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

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

	mapPath := parentPath + tag + ".[name]."
	valueType := fieldType.Elem()

	if valueType.Kind() == reflect.Struct {
		zeroValue := reflect.New(valueType).Elem()
		walkAndBuild(valueType, zeroValue, mapPath, entries, buildMdEntry, buildMdMapEntry, buildMdChildPath)
	}
}

func buildMdChildPath(parent string, child string) string {
	return parent + strings.ToLower(child) + "."
}

func compileMd(entries []MarkdownEntry) []byte {
	buffer := strings.Builder{}

	buffer.WriteString("{/* This part is automatically generated by gen/gen_md.go in the main repository. DO NOT EDIT. */}\n\n")
	buffer.WriteString("import { Tabs, TabItem } from '@astrojs/starlight/components';\n\n")
	buffer.WriteString("<Tabs>\n")

	renderTabItem(&buffer, entries, "Environment")
	renderTabItem(&buffer, entries, "Flags")
	renderTabItem(&buffer, entries, "YAML")

	buffer.WriteString("</Tabs>\n")

	return []byte(buffer.String())
}

func renderTabItem(buffer *strings.Builder, entries []MarkdownEntry, section string) {
	buffer.WriteString(fmt.Sprintf("  <TabItem label=\"%s\">\n", section))
	buffer.WriteString("    ### main\n\n")
	buffer.WriteString("    | Option | Description | Default |\n")
	buffer.WriteString("    | - | - | - |\n")

	configType := strings.ToLower(section)
	previousSection := ""

	for _, entry := range entries {
		section := strings.Split(entry.Options.YAML, ".")[0]
		var option string
		switch configType {
		case "yaml":
			option = entry.Options.YAML
		case "flags":
			option = entry.Options.Flag
		case "environment":
			option = entry.Options.Env
		}
		if option == "" {
			continue
		}
		if strings.Count(entry.Options.YAML, ".") >= 1 {
			if section != previousSection {
				buffer.WriteString("\n    ### " + strings.ToLower(section) + "\n\n")
				buffer.WriteString("    | Option | Description | Default |\n")
				buffer.WriteString("    | - | - | - |\n")
				previousSection = section
			}
		}
		fmt.Fprintf(buffer, "    | `%s` | %s | %s |\n", option, entry.Description, entry.Default)
	}

	buffer.WriteString("  </TabItem>\n")
}
