package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
	"text/template"
	"time"
	"sort"

    {{- range $key, $value := .}}
    pkg{{$key}} "{{$value}}"
    {{- end}}
)

// ConfigField is the representation of one configuration field
type ConfigField struct {
	EnvVars         	[]string
	DefaultValue    	string
	IntroductionVersion	string
	Type            	string
	Description     	string
	VersionInfo     	string
	DeprecationLink 	string
}

// DeprecationField holds information about deprecation
type DeprecationField struct {
	DeprecationVersion     string
	DeprecationInfo        string
	DeprecationReplacement string
	RemovalVersion         string
}

// EnvVar holds information about one envvar
type EnvVar struct {
	Name                string
	IntroductionVersion string
	DefaultValue        string
	Type                string
	Description         string
	Services            []string
}

type templateData struct {
	ExtensionName   string
	Fields          []ConfigField
	Deprecations    []DeprecationField
	HasDeprecations bool
	Timestamp 	string
}

func main() {
	fmt.Println("Generating adoc documentation for environment variables:")
	adoc, err := os.ReadFile("../../docs/templates/ADOC.tmpl")
	if err != nil {
	    log.Fatal(err)
	}
	dpr, err := os.ReadFile("../../docs/templates/ADOC_deprecation.tmpl")
	if err != nil {
	    log.Fatal(err)
	}
	replacer := strings.NewReplacer(
			"github.com/owncloud/ocis/v2/services/", "",
			"/pkg/config/defaults", "",
	)
	var (
	    fields 	 []ConfigField
	    deprecations []DeprecationField
	    adocFile  	 *os.File
	    dprFile 	 *os.File
	)
	adoctpl := template.Must(template.New("").Parse(string(adoc)))
	dprtpl := template.Must(template.New("").Parse(string(dpr)))

	m := map[string]interface{}{
	{{- range $key, $value := .}}
		"{{$value}}": *pkg{{$key}}.FullDefaultConfig(),
	{{- end }}
	}

	targetFolder := "../../docs/services/_includes/adoc/"
	all := make(map[string]EnvVar)
	for pkg, conf := range m {
	    service := replacer.Replace(pkg)
	    timestamp := time.Now().Format("2006-01-02-15-04-05")
	    fields, deprecations = GetAnnotatedVariables(conf, timestamp)
	    var hasDeprecations bool
	    if len(deprecations) > 0 {
		    hasDeprecations = true
	    }

	    for _, f := range fields {
		for _, e := range f.EnvVars {
		    if env, ok := all[e]; ok {
			env.Services = append(env.Services, service)
			sort.Slice(env.Services, func(i, j int) bool {
			    return env.Services[i] < env.Services[j]
			})
			all[e] = env
		    } else {
			all[e] = EnvVar{
			    Name:         e,
				IntroductionVersion: replaceEnvVarPlaceHolder(f.IntroductionVersion),
			    Description:  f.Description,
			    Type: 	  f.Type,
			    DefaultValue: f.DefaultValue,
			    Services:     []string{service},
			}
		    }
		}
	    }

	    if len(fields) > 0 || len(deprecations) > 0 {
		fmt.Printf("... %s\n", pkg)
		td := templateData{
			ExtensionName: service,
			Fields:        fields,
			Deprecations: deprecations,
			HasDeprecations: hasDeprecations,
			Timestamp: timestamp,
		}
		adocFile, err = os.Create(filepath.Join(targetFolder, service + "_configvars.adoc"))
		if err != nil {
			log.Fatalf("Failed to create target file: %s", err)
		}
		defer adocFile.Close()

		if err := adoctpl.Execute(adocFile, td); err != nil {
			log.Fatalf("Failed to execute template: %s", err)
		}

		dprFile, err = os.Create(filepath.Join(targetFolder, service + "_deprecation.adoc"))
		if err != nil {
			log.Fatalf("Failed to create target file: %s", err)
		}
		defer dprFile.Close()

		if err := dprtpl.Execute(dprFile, td); err != nil {
			log.Fatalf("Failed to execute template: %s", err)
		}
	    }
	}

	// render global env vars
	tmplValues := make([]map[string]interface{}, 0)
	for _, env := range all {
	    if len(env.Services) > 1 {
		tmplValues = append(tmplValues, map[string]interface{}{
			"Name":         env.Name,
			"IntroductionVersion": replaceEnvVarPlaceHolder(env.IntroductionVersion),
			"Services":     env.Services,
			"Description":  env.Description,
			"DefaultValue": env.DefaultValue,
			"Type":         env.Type,
		})
	    }
	}

	// sort
	sort.Slice(tmplValues, func(i, j int) bool {
		return tmplValues[i]["Name"].(string) < tmplValues[j]["Name"].(string)
	})

	glc, err := os.ReadFile("../../docs/templates/ADOC_global.tmpl")
	if err != nil {
	    log.Fatal(err)
	}

	gltpl := template.Must(template.New("").Parse(string(glc)))
	glfile, err := os.Create(filepath.Join(targetFolder, "global_configvars.adoc"))
	if err != nil {
		log.Fatalf("Failed to create target file: %s", err)
	}

	if err := gltpl.Execute(glfile, tmplValues); err != nil {
	    log.Printf("Failed to execute template: %s", err)
	}

	fmt.Println("done")
}

func replaceEnvVarPlaceHolder(s string) string {
	return strings.Replace(
		s,
		"%%NEXT_PRODUCTION_VERSION%%",
		"next-prod",
		-1,
	)
}

func GetAnnotatedVariables(s interface{}, timestamp string) ([]ConfigField, []DeprecationField) {
	t := reflect.TypeOf(s)
	v := reflect.ValueOf(s)

	var fields []ConfigField
	var deprecations []DeprecationField
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		value := v.Field(i)

		switch value.Kind() {
		default:
			desc := field.Tag.Get("desc")
			env, ok := field.Tag.Lookup("env")
			deprecationLink := ""
			if !ok {
				continue
			}
			introductionVersion, _ := field.Tag.Lookup("introductionVersion")
			deprecationVersion, _ := field.Tag.Lookup("deprecationVersion")
			removalVersion, _ := field.Tag.Lookup("removalVersion")
			deprecationInfo, _ := field.Tag.Lookup("deprecationInfo")
			deprecationReplacement, _ := field.Tag.Lookup("deprecationReplacement")
			if deprecationVersion != "" ||
				removalVersion != "" ||
				deprecationInfo != "" ||
				deprecationReplacement != "" {
				deprecationLink = fmt.Sprintf("xref:deprecation-note-%s[Deprecation Note]", timestamp)
			}
			v := fmt.Sprintf("%v", value.Interface())
			td := strings.Split(env, ";")
			// re := regexp.MustCompile(`^(https?:\/\/)`)
			// v = re.ReplaceAllString(v,"\\$1")
			re := regexp.MustCompile(`(https?:\/\/)`)
			desc = re.ReplaceAllString(desc, "\\$1")
			re = regexp.MustCompile(`(\|)`)
			v = re.ReplaceAllString(v, "\\$1")
			typeName := value.Type().Name()
			if typeName == "" {
				typeName = value.Type().String()
			}
			fields = append(fields,
				ConfigField{
					EnvVars:         td,
					IntroductionVersion: replaceEnvVarPlaceHolder(introductionVersion),
					DefaultValue:    v,
					Description:     desc,
					Type:            typeName,
					DeprecationLink: deprecationLink,
				})
			if deprecationLink != "" {
				deprecations = append(deprecations,
					DeprecationField{
						DeprecationVersion:     replaceEnvVarPlaceHolder(deprecationVersion),
						DeprecationInfo:        deprecationInfo,
						DeprecationReplacement: deprecationReplacement,
						RemovalVersion:         replaceEnvVarPlaceHolder(removalVersion),
					})
			}
		case reflect.Ptr:
			// PolicySelectors in the Proxy are being skipped atm
			// they are not configurable via env vars, if that changes
			// they are probably added to the Sanitize() function
			// and this should not be an issue then
			if !value.IsZero() && value.Elem().CanInterface() {
				f, d := GetAnnotatedVariables(value.Elem().Interface(), timestamp)
				fields = append(fields, f...)
				deprecations = append(deprecations, d...)
			}
		case reflect.Struct:
			f, d := GetAnnotatedVariables(value.Interface(), timestamp)
			fields = append(fields, f...)
			deprecations = append(deprecations, d...)
		}
	}
	return fields, deprecations
}
