// SPDX-License-Identifier: AGPL-3.0-only
// Provenance-includes-location: https://github.com/grafana/cortex-tools/blob/main/pkg/printer/printer.go
// Provenance-includes-license: Apache-2.0
// Provenance-includes-copyright: The Cortex Authors.

package printer

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sort"
	"strings"
	"text/tabwriter"

	"github.com/alecthomas/chroma/quick"
	"github.com/mitchellh/colorstring"
	"gopkg.in/yaml.v3"

	"github.com/grafana/mimir/pkg/mimirtool/rules"
	"github.com/grafana/mimir/pkg/mimirtool/rules/rwrulefmt"
)

// Printer is  used for printing formatted output from the mimirtool
type Printer struct {
	disableColor bool
	colorizer    colorstring.Colorize
}

// New returns a Printer struct
func New(color bool) *Printer {
	return &Printer{
		disableColor: color,
		colorizer: colorstring.Colorize{
			Colors:  colorstring.DefaultColors,
			Reset:   true,
			Disable: color,
		},
	}
}

// Println is a convenience wrapper for fmt.Println with support for color
// codes.
func (p *Printer) Println(a string) {
	fmt.Println(p.colorizer.Color(a))
}

// Printf is a convenience wrapper for fmt.Printf with support for color
// codes.
func (p *Printer) Printf(format string, a ...interface{}) {
	fmt.Printf(p.colorizer.Color(format), a...)
}

// PrintAlertmanagerConfig prints the current alertmanager config
func (p *Printer) PrintAlertmanagerConfig(config string, templates map[string]string) error {

	// go-text-template
	if !p.disableColor {
		err := quick.Highlight(os.Stdout, config, "yaml", "terminal", "swapoff")
		if err != nil {
			return err
		}
	} else {
		fmt.Println(string(config))
	}

	fmt.Printf("\nTemplates:\n")
	for fn, template := range templates {
		fmt.Println(fn + ":")
		if !p.disableColor {
			err := quick.Highlight(os.Stdout, template, "go-text-template", "terminal", "swapoff")
			if err != nil {
				return nil
			}
		} else {
			fmt.Println(template)
		}
	}

	return nil
}

// PrintRuleGroups prints the current alertmanager config
func (p *Printer) PrintRuleGroups(rules map[string][]rwrulefmt.RuleGroup) error {
	encodedRules, err := yaml.Marshal(&rules)
	if err != nil {
		return err
	}

	// go-text-template
	if !p.disableColor {
		return quick.Highlight(os.Stdout, string(encodedRules), "yaml", "terminal", "swapoff")
	}

	fmt.Println(string(encodedRules))

	return nil
}

// PrintRuleGroup prints the current alertmanager config
func (p *Printer) PrintRuleGroup(rule rwrulefmt.RuleGroup) error {
	encodedRule, err := yaml.Marshal(&rule)
	if err != nil {
		return err
	}

	// go-text-template
	if !p.disableColor {
		return quick.Highlight(os.Stdout, string(encodedRule), "yaml", "terminal", "swapoff")
	}

	fmt.Println(string(encodedRule))

	return nil
}

// PrintComparisonResult prints the differences between the staged rules namespace
// and active rules namespace
func (p *Printer) PrintComparisonResult(results []rules.NamespaceChange, verbose bool) error {
	created, updated, deleted := rules.SummarizeChanges(results)

	// If any changes are detected, print the symbol legend
	if (created + updated + deleted) > 0 {
		fmt.Println("Changes are indicated with the following symbols:")
		if created > 0 {
			p.Println("[green]  +[reset] created")
		}
		if updated > 0 {
			p.Println("[yellow]  ~[reset] updated")
		}
		if deleted > 0 {
			p.Println("[red]  -[reset] deleted")
		}
		fmt.Println()
		fmt.Println("The following changes will be made if the provided rule set is synced:")
	} else {
		fmt.Println("no changes detected")
		return nil
	}

	for _, change := range results {
		switch change.State {
		case rules.Created:
			p.Printf("[green]+ Namespace: %v\n", change.Namespace)
			for _, c := range change.GroupsCreated {
				p.Printf("[green]  + Group: %v\n", c.Name)
			}
		case rules.Updated:
			p.Printf("[yellow]~ Namespace: %v\n", change.Namespace)
			for _, c := range change.GroupsCreated {
				p.Printf("[green]  + Group: %v\n", c.Name)
			}

			for _, c := range change.GroupsUpdated {
				p.Printf("[yellow]  ~ Group: %v\n", c.New.Name)

				// Print the full diff of the rules if verbose is set
				if verbose {
					newYaml, _ := yaml.Marshal(c.New)
					separated := strings.Split(string(newYaml), "\n")
					for _, l := range separated {
						p.Printf("[green]+ %v\n", l)
					}

					oldYaml, _ := yaml.Marshal(c.Original)
					separated = strings.Split(string(oldYaml), "\n")
					for _, l := range separated {
						p.Printf("[red]- %v\n", l)
					}
				}
			}

			for _, c := range change.GroupsDeleted {
				p.Printf("[red]  - Group: %v\n", c.Name)
			}
		case rules.Deleted:
			p.Printf("[red]- Namespace: %v\n", change.Namespace)
			for _, c := range change.GroupsDeleted {
				p.Printf("[red]  - Group: %v\n", c.Name)
			}
		}
	}

	fmt.Println()
	fmt.Printf("Diff Summary: %v Groups Created, %v Groups Updated, %v Groups Deleted\n", created, updated, deleted)
	return nil
}

func (p *Printer) PrintRuleSet(rules map[string][]rwrulefmt.RuleGroup, format string, writer io.Writer) error {
	nsKeys := make([]string, 0, len(rules))
	for k := range rules {
		nsKeys = append(nsKeys, k)
	}
	sort.Strings(nsKeys)

	type namespaceAndRuleGroup struct {
		Namespace string `json:"namespace" yaml:"namespace"`
		RuleGroup string `json:"rulegroup" yaml:"rulegroup"`
	}
	var items []namespaceAndRuleGroup

	for _, ns := range nsKeys {
		for _, rg := range rules[ns] {
			items = append(items, namespaceAndRuleGroup{
				Namespace: ns,
				RuleGroup: rg.Name,
			})
		}
	}

	switch format {
	case "json":
		output, err := json.Marshal(items)
		if err != nil {
			return err
		}

		// go-text-template
		if !p.disableColor {
			return quick.Highlight(writer, string(output), "json", "terminal", "swapoff")
		}

		fmt.Fprint(writer, string(output))
	case "yaml":
		output, err := yaml.Marshal(items)
		if err != nil {
			return err
		}

		// go-text-template
		if !p.disableColor {
			return quick.Highlight(writer, string(output), "yaml", "terminal", "swapoff")
		}

		fmt.Fprint(writer, string(output))
	default:
		w := tabwriter.NewWriter(writer, 0, 0, 1, ' ', tabwriter.Debug)

		fmt.Fprintln(w, "Namespace\t Rule Group")
		for _, item := range items {
			fmt.Fprintf(w, "%s\t %s\n", item.Namespace, item.RuleGroup)
		}

		w.Flush()
	}

	return nil
}
