package main

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"time"
)

const monitoringRepository = "https://github.com/pingcap/monitoring"

type bundleManifest struct {
	Repository      string          `json:"repository"`
	Revision        string          `json:"revision"`
	ArchiveSHA256   string          `json:"archive_sha256,omitempty"`
	Files           []dashboardFile `json:"files"`
	Transformations []string        `json:"transformations"`
}

type dashboardFile struct {
	Name           string `json:"name"`
	Title          string `json:"title"`
	UID            string `json:"uid,omitempty"`
	UpstreamSHA256 string `json:"upstream_sha256"`
	BundleSHA256   string `json:"bundle_sha256"`
	Support        string `json:"support"`
	Reason         string `json:"reason,omitempty"`
}

func main() {
	var source string
	var revision string
	var archivePath string
	var manifestPath string
	flag.StringVar(&source, "source", "", "directory containing the pinned upstream dashboard JSON files")
	flag.StringVar(&revision, "revision", "", "full pinned pingcap/monitoring commit")
	flag.StringVar(&archivePath, "archive", "", "output deterministic .tar.gz path")
	flag.StringVar(&manifestPath, "manifest", "", "output provenance JSON path")
	flag.Parse()
	if strings.TrimSpace(source) == "" || strings.TrimSpace(revision) == "" ||
		strings.TrimSpace(archivePath) == "" || strings.TrimSpace(manifestPath) == "" {
		fatalf("source, revision, archive, and manifest are required")
	}

	entries, err := os.ReadDir(source)
	if err != nil {
		fatalf("read dashboard source: %v", err)
	}
	names := make([]string, 0, len(entries))
	for _, entry := range entries {
		if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".json") {
			names = append(names, entry.Name())
		}
	}
	slices.Sort(names)
	if len(names) != 25 {
		fatalf("dashboard source contains %d JSON files, want 25", len(names))
	}

	manifest := bundleManifest{
		Repository: monitoringRepository,
		Revision:   revision,
		Transformations: []string{
			"pods.json: inject a multi-select namespace variable and rewrite namespace=$namespace PromQL matchers to regex for tidb-serverless plus both product-owned TiDB serving lanes",
		},
	}
	files := make(map[string][]byte, len(names))
	titles := map[string]string{}
	for _, name := range names {
		upstream, err := os.ReadFile(filepath.Join(source, name))
		if err != nil {
			fatalf("read %s: %v", name, err)
		}
		var dashboard map[string]any
		if err := json.Unmarshal(upstream, &dashboard); err != nil {
			fatalf("parse %s: %v", name, err)
		}
		if name == "pods.json" {
			injectPodsNamespaceVariable(dashboard)
		}
		bundled, err := json.Marshal(dashboard)
		if err != nil {
			fatalf("marshal %s: %v", name, err)
		}
		title, ok := dashboard["title"].(string)
		if !ok || strings.TrimSpace(title) == "" {
			fatalf("%s has no title", name)
		}
		if prior, exists := titles[title]; exists {
			fatalf("dashboard title %q is duplicated by %s and %s", title, prior, name)
		}
		titles[title] = name
		uid, ok := dashboard["uid"].(string)
		if !ok && dashboard["uid"] != nil {
			fatalf("%s has a non-string uid", name)
		}
		support, reason := dashboardSupport(name)
		manifest.Files = append(manifest.Files, dashboardFile{
			Name:           name,
			Title:          title,
			UID:            uid,
			UpstreamSHA256: sha256Hex(upstream),
			BundleSHA256:   sha256Hex(bundled),
			Support:        support,
			Reason:         reason,
		})
		files[filepath.ToSlash(filepath.Join("dashboards", name))] = bundled
	}

	insideManifest, err := json.MarshalIndent(manifest, "", "  ")
	if err != nil {
		fatalf("marshal embedded bundle manifest: %v", err)
	}
	files["bundle.json"] = append(insideManifest, '\n')
	archive, err := deterministicTarGzip(files)
	if err != nil {
		fatalf("build archive: %v", err)
	}
	manifest.ArchiveSHA256 = sha256Hex(archive)
	externalManifest, err := json.MarshalIndent(manifest, "", "  ")
	if err != nil {
		fatalf("marshal external bundle manifest: %v", err)
	}
	externalManifest = append(externalManifest, '\n')

	if err := os.MkdirAll(filepath.Dir(archivePath), 0o755); err != nil {
		fatalf("create archive directory: %v", err)
	}
	if err := os.WriteFile(archivePath, archive, 0o600); err != nil {
		fatalf("write archive: %v", err)
	}
	if err := os.MkdirAll(filepath.Dir(manifestPath), 0o755); err != nil {
		fatalf("create manifest directory: %v", err)
	}
	if err := os.WriteFile(manifestPath, externalManifest, 0o600); err != nil {
		fatalf("write manifest: %v", err)
	}
}

func injectPodsNamespaceVariable(dashboard map[string]any) {
	if rewritten := rewritePodsNamespaceMatchers(dashboard); rewritten == 0 {
		fatalf("pods.json contains no namespace=$namespace PromQL matchers to rewrite")
	}
	templating, ok := dashboard["templating"].(map[string]any)
	if !ok {
		fatalf("pods.json templating object is missing")
	}
	list, ok := templating["list"].([]any)
	if !ok {
		fatalf("pods.json templating.list is missing")
	}
	for _, item := range list {
		variable, ok := item.(map[string]any)
		if !ok {
			fatalf("pods.json templating.list contains a non-object variable")
		}
		if variable["name"] == "namespace" {
			fatalf("pods.json unexpectedly already declares namespace")
		}
	}
	variable := map[string]any{
		"allValue":    ".*",
		"current":     map[string]any{"text": "All", "value": "$__all"},
		"datasource":  "k8s-cluster",
		"definition":  `label_values(kube_pod_info{cluster="$cluster",namespace=~"tidb-serverless|tidb-pool|super-vip-tidb-pool"}, namespace)`,
		"hide":        0,
		"includeAll":  true,
		"label":       "Namespace",
		"multi":       true,
		"name":        "namespace",
		"options":     []any{},
		"query":       `label_values(kube_pod_info{cluster="$cluster",namespace=~"tidb-serverless|tidb-pool|super-vip-tidb-pool"}, namespace)`,
		"refresh":     1,
		"regex":       "",
		"skipUrlSync": false,
		"sort":        1,
		"type":        "query",
	}
	insertAt := 1
	list = append(list, nil)
	copy(list[insertAt+1:], list[insertAt:])
	list[insertAt] = variable
	templating["list"] = list
}

func rewritePodsNamespaceMatchers(value any) int {
	const exact = `namespace="$namespace"`
	const regex = `namespace=~"$namespace"`
	rewritten := 0
	var walk func(any)
	walk = func(current any) {
		switch typed := current.(type) {
		case map[string]any:
			for key, child := range typed {
				if text, ok := child.(string); ok {
					count := strings.Count(text, exact)
					if count > 0 {
						typed[key] = strings.ReplaceAll(text, exact, regex)
						rewritten += count
					}
					continue
				}
				walk(child)
			}
		case []any:
			for index, child := range typed {
				if text, ok := child.(string); ok {
					count := strings.Count(text, exact)
					if count > 0 {
						typed[index] = strings.ReplaceAll(text, exact, regex)
						rewritten += count
					}
					continue
				}
				walk(child)
			}
		}
	}
	walk(value)
	return rewritten
}

func dashboardSupport(name string) (support, reason string) {
	core := map[string]struct{}{
		"overview.json": {}, "performance_overview.json": {}, "pd.json": {},
		"tidb.json": {}, "tidb_runtime.json": {}, "tidb_resource_control.json": {},
		"tikv_details.json": {}, "tikv_summary.json": {}, "tikv_trouble_shooting.json": {},
		"nodes.json": {},
	}
	if _, ok := core[name]; ok {
		return "supported", ""
	}
	switch name {
	case "pods.json":
		return "partial", "namespace compatibility variable injected; obsolete kube-state-metrics query families remain visible"
	case "tiproxy_summary.json":
		return "unsupported", "Starter gateway is not TiProxy and exposes no TiProxy metrics endpoint"
	case "DM-Monitor-Standard.json", "DM-Monitor-Professional.json":
		return "conditional", "DM master is discoverable but DM worker replicas and workload are absent"
	default:
		return "unsupported", "component is not present in the Starter Minimal substrate"
	}
}

func deterministicTarGzip(files map[string][]byte) ([]byte, error) {
	names := make([]string, 0, len(files))
	for name := range files {
		names = append(names, name)
	}
	slices.Sort(names)
	var compressed bytes.Buffer
	gz, err := gzip.NewWriterLevel(&compressed, gzip.BestCompression)
	if err != nil {
		return nil, err
	}
	gz.ModTime = time.Unix(0, 0).UTC()
	gz.OS = 255
	tw := tar.NewWriter(gz)
	for _, name := range names {
		data := files[name]
		header := &tar.Header{
			Name: name, Mode: 0o644, Size: int64(len(data)),
			ModTime: time.Unix(0, 0).UTC(),
			Uid:     0, Gid: 0, Uname: "root", Gname: "root", Format: tar.FormatUSTAR,
		}
		if err := tw.WriteHeader(header); err != nil {
			return nil, err
		}
		if _, err := tw.Write(data); err != nil {
			return nil, err
		}
	}
	if err := tw.Close(); err != nil {
		return nil, err
	}
	if err := gz.Close(); err != nil {
		return nil, err
	}
	return compressed.Bytes(), nil
}

func sha256Hex(data []byte) string {
	sum := sha256.Sum256(data)
	return hex.EncodeToString(sum[:])
}

func fatalf(format string, args ...any) {
	fmt.Fprintf(os.Stderr, format+"\n", args...)
	os.Exit(1) //nolint:revive,nolintlint // Required by revive on platforms where deep-exit runs.
}
