package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"

	"github.com/dave/jennifer/jen"
)

const (
	outputPackage = "grype/db/internal/codename"
	outputPath    = "internal/codename/codenames_generated.go" // relative to where go generate is called
)

type Version struct {
	Cycle    string `json:"cycle"`
	Codename string `json:"codename"`
}

func main() {
	osCodenames := make(map[string]map[string]map[string]string)

	fmt.Println("Fetching and parsing data for operating system codenames")

	fmt.Println("ubuntu:")
	osCodenames["ubuntu"] = fetchAndParse("https://endoflife.date/api/ubuntu.json", ubuntuHandler)

	fmt.Println("debian:")
	osCodenames["debian"] = fetchAndParse("https://endoflife.date/api/debian.json", lowercaseHandler)

	fmt.Printf("Generating code for %d operating system codenames\n", len(osCodenames))

	f := jen.NewFile("codename")
	f.HeaderComment("DO NOT EDIT: generated by grype/db/internal/codename/generate/main.go")
	f.ImportName(outputPackage, "pkg")
	f.Var().Id("normalizedOSCodenames").Op("=").Map(jen.String()).Map(jen.String()).Map(jen.String()).String().Values(jen.DictFunc(func(d jen.Dict) {
		for osName, versions := range osCodenames {
			majorMap := jen.Dict{}
			for major, minors := range versions {
				minorMap := jen.Dict{}
				for minor, codename := range minors {
					minorMap[jen.Lit(minor)] = jen.Lit(codename)
				}
				majorMap[jen.Lit(major)] = jen.Values(minorMap)
			}
			d[jen.Lit(osName)] = jen.Values(majorMap)
		}
	}))

	rendered := fmt.Sprintf("%#v", f)

	file, err := os.OpenFile(outputPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		panic(fmt.Errorf("unable to open file: %w", err))
	}
	defer file.Close()

	if _, err := file.WriteString(rendered); err != nil {
		panic(fmt.Errorf("unable to write file: %w", err))
	}

	fmt.Printf("Code generation completed and written to %s\n", outputPath)
}

// fetchAndParse fetches the JSON data from a URL, parses it, and organizes it into a map.
func fetchAndParse(url string, handler func(string) string) map[string]map[string]string {
	resp, err := http.Get(url) //nolint:gosec
	if err != nil {
		panic(fmt.Errorf("error fetching data from %s: %w", url, err))
	}
	defer resp.Body.Close()

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(fmt.Errorf("error reading response: %w", err))
	}

	var versions []Version
	if err := json.Unmarshal(data, &versions); err != nil {
		panic(fmt.Errorf("error parsing JSON: %w", err))
	}

	parsedData := make(map[string]map[string]string)
	for _, version := range versions {
		major, minor := parseVersion(version.Cycle)
		if parsedData[major] == nil {
			parsedData[major] = make(map[string]string)
		}
		codename := handler(version.Codename)
		fmt.Printf("  adding %s.%s --> %s\n", major, minor, codename)
		parsedData[major][minor] = codename
	}

	return parsedData
}

func lowercaseHandler(codename string) string {
	return strings.ToLower(codename)
}

func ubuntuHandler(codename string) string {
	return strings.ToLower(strings.Split(codename, " ")[0])
}

// parseVersion splits a version string like "20.04" into major "20" and minor "04".
func parseVersion(version string) (string, string) {
	parts := strings.Split(version, ".")
	major := strings.TrimLeft(parts[0], "0")
	minor := "*"
	if len(parts) > 1 {
		if parts[1] == "0" {
			minor = parts[1]
		} else {
			minor = strings.TrimLeft(parts[1], "0")
		}
	}
	return major, minor
}
