package main

//go:generate go run .

import (
	"bufio"
	"bytes"
	"embed"
	"errors"
	"fmt"
	"go/format"
	html "html/template"
	"log"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"text/template"

	"github.com/go-acme/lego/v4/internal/dns/descriptors"
)

//go:embed templates
var templateFS embed.FS

const (
	root = "../../../"

	cliOutput  = root + "cmd/zz_gen_cmd_dnshelp.go"
	docOutput  = root + "docs/content/dns"
	readmePath = root + "README.md"
)

const (
	mdTemplate     = "templates/dns.md.tmpl"
	cliTemplate    = "templates/dns.go.tmpl"
	readmeTemplate = "templates/readme.md.tmpl"
)

const (
	startLine = "<!-- START DNS PROVIDERS LIST -->"
	endLine   = "<!-- END DNS PROVIDERS LIST -->"
)

func main() {
	models, err := descriptors.GetProviderInformation(root)
	if err != nil {
		log.Fatal(err)
	}

	err = cleanDocumentation()
	if err != nil {
		log.Fatal(err)
	}

	for _, m := range models.Providers {
		// generate documentation
		err = generateDocumentation(m)
		if err != nil {
			log.Fatal(err)
		}
	}

	// generate CLI help
	err = generateCLIHelp(models)
	if err != nil {
		log.Fatal(err)
	}

	// generate README.md
	err = generateReadMe(models)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Documentation for %d DNS providers has been generated.\n", len(models.Providers)+1)
}

func cleanDocumentation() error {
	paths, err := filepath.Glob(filepath.Join(docOutput, "zz_gen_*.md"))
	if err != nil {
		return err
	}

	for _, p := range paths {
		err = os.RemoveAll(p)
		if err != nil {
			return err
		}
	}

	return nil
}

func generateDocumentation(m descriptors.Provider) error {
	filename := filepath.Join(docOutput, "zz_gen_"+m.Code+".md")

	file, err := os.Create(filename)
	if err != nil {
		return err
	}

	defer func() { _ = file.Close() }()

	return template.Must(template.ParseFS(templateFS, mdTemplate)).Execute(file, m)
}

func generateCLIHelp(models *descriptors.Providers) error {
	filename := filepath.Clean(cliOutput)

	file, err := os.Create(filename)
	if err != nil {
		return err
	}

	defer func() { _ = file.Close() }()

	b := &bytes.Buffer{}

	err = template.Must(
		template.New(filepath.Base(cliTemplate)).Funcs(map[string]any{
			"safe": func(src string) string {
				return strings.ReplaceAll(src, "`", "'")
			},
		}).ParseFS(templateFS, cliTemplate),
	).Execute(b, models)
	if err != nil {
		return err
	}

	// gofmt
	source, err := format.Source(b.Bytes())
	if err != nil {
		return err
	}

	_, err = file.Write(source)

	return err
}

func generateReadMe(models *descriptors.Providers) error {
	tpl := html.Must(html.New(filepath.Base(readmeTemplate)).ParseFS(templateFS, readmeTemplate))
	providers := orderProviders(models)

	file, err := os.Open(readmePath)
	if err != nil {
		return err
	}

	defer func() { _ = file.Close() }()

	var skip bool

	buffer := bytes.NewBufferString("")

	fileScanner := bufio.NewScanner(file)
	for fileScanner.Scan() {
		text := fileScanner.Text()

		if text == startLine {
			_, _ = fmt.Fprintln(buffer, text)
			if err = tpl.Execute(buffer, providers); err != nil {
				return err
			}

			skip = true
		}

		if text == endLine {
			skip = false
		}

		if skip {
			continue
		}

		_, _ = fmt.Fprintln(buffer, text)
	}

	if fileScanner.Err() != nil {
		return fileScanner.Err()
	}

	if skip {
		return errors.New("missing end tag")
	}

	return os.WriteFile(readmePath, buffer.Bytes(), 0o666)
}

func orderProviders(models *descriptors.Providers) [][]descriptors.Provider {
	const nbCol = 4

	slices.SortFunc(models.Providers, func(a, b descriptors.Provider) int {
		return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
	})

	var (
		matrix [][]descriptors.Provider
		row    []descriptors.Provider
	)

	for i, p := range models.Providers {
		switch {
		case len(row) == nbCol:
			matrix = append(matrix, row)
			row = []descriptors.Provider{p}

		case i == len(models.Providers)-1:
			row = append(row, p)
			for j := len(row); j < nbCol; j++ {
				row = append(row, descriptors.Provider{})
			}

			matrix = append(matrix, row)

		default:
			row = append(row, p)
		}
	}

	if len(row) < nbCol {
		for j := len(row); j < nbCol; j++ {
			row = append(row, descriptors.Provider{})
		}

		matrix = append(matrix, row)
	}

	return matrix
}
