package render

import (
	"fmt"
	"math"
	"strconv"
	"strings"

	"github.com/derailed/k9s/internal/client"
	"github.com/derailed/popeye/pkg/config"
	"github.com/derailed/tview"
	"github.com/gdamore/tcell/v2"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

// Popeye renders a sanitizer to screen.
type Popeye struct{}

// ColorerFunc colors a resource row.
func (Popeye) ColorerFunc() ColorerFunc {
	return func(ns string, h Header, re RowEvent) tcell.Color {
		c := DefaultColorer(ns, h, re)

		warnCol := h.IndexOf("WARNING", true)
		status, _ := strconv.Atoi(strings.TrimSpace(re.Row.Fields[warnCol]))
		if status > 0 {
			c = tcell.ColorOrange
		}
		errCol := h.IndexOf("ERROR", true)
		status, _ = strconv.Atoi(strings.TrimSpace(re.Row.Fields[errCol]))
		if status > 0 {
			c = ErrColor
		}
		return c
	}
}

// Header returns a header row.
func (Popeye) Header(ns string) Header {
	return Header{
		HeaderColumn{Name: "RESOURCE"},
		HeaderColumn{Name: "SCORE%", Align: tview.AlignRight},
		HeaderColumn{Name: "SCANNED", Align: tview.AlignRight},
		HeaderColumn{Name: "ERROR", Align: tview.AlignRight},
		HeaderColumn{Name: "WARNING", Align: tview.AlignRight},
		HeaderColumn{Name: "INFO", Align: tview.AlignRight},
		HeaderColumn{Name: "OK", Align: tview.AlignRight},
	}
}

// Render renders a K8s resource to screen.
func (Popeye) Render(o interface{}, ns string, r *Row) error {
	s, ok := o.(Section)
	if !ok {
		return fmt.Errorf("expected Section, but got %T", o)
	}

	r.ID = client.FQN(ns, s.Title)
	r.Fields = append(r.Fields,
		s.Title,
		strconv.Itoa(s.Tally.Score()),
		strconv.Itoa(s.Tally.OK+s.Tally.Info+s.Tally.Warning+s.Tally.Error),
		strconv.Itoa(s.Tally.Error),
		strconv.Itoa(s.Tally.Warning),
		strconv.Itoa(s.Tally.Info),
		strconv.Itoa(s.Tally.OK),
	)
	return nil
}

// ----------------------------------------------------------------------------
// Helpers...

type (
	// Builder represents a popeye report.
	Builder struct {
		Report Report `json:"popeye" yaml:"popeye"`
	}

	// Report represents the output of a sanitization pass.
	Report struct {
		Score    int      `json:"score" yaml:"score"`
		Grade    string   `json:"grade" yaml:"grade"`
		Sections Sections `json:"sanitizers,omitempty" yaml:"sanitizers,omitempty"`
	}

	// Sections represents a collection of sections.
	Sections []Section

	// Section represents a sanitizer pass.
	Section struct {
		Title   string  `json:"sanitizer" yaml:"sanitizer"`
		GVR     string  `yaml:"gvr" json:"gvr"`
		Tally   *Tally  `json:"tally" yaml:"tally"`
		Outcome Outcome `json:"issues,omitempty" yaml:"issues,omitempty"`
	}

	// Outcome represents a classification of reports outcome.
	Outcome map[string]Issues

	// Issues represents a collection of issues.
	Issues []Issue

	// Issue represents a sanitization issue.
	Issue struct {
		Group   string       `yaml:"group" json:"group"`
		GVR     string       `yaml:"gvr" json:"gvr"`
		Level   config.Level `yaml:"level" json:"level"`
		Message string       `yaml:"message" json:"message"`
	}

	// Tally tracks a section scores.
	Tally struct {
		OK, Info, Warning, Error int
		Count                    int
	}
)

// Sum sums up tally counts.
func (t *Tally) Sum() int {
	return t.OK + t.Info + t.Warning + t.Error
}

// Score returns the overall sections score in percent.
func (t *Tally) Score() int {
	oks := t.OK + t.Info
	return toPerc(float64(oks), float64(oks+t.Warning+t.Error))
}

func toPerc(v1, v2 float64) int {
	if v2 == 0 {
		return 0
	}
	return int(math.Floor((v1 / v2) * 100))
}

// Len returns a section length.
func (s Sections) Len() int {
	return len(s)
}

// Swap swaps values.
func (s Sections) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

// Less compares section scores.
func (s Sections) Less(i, j int) bool {
	t1, t2 := s[i].Tally, s[j].Tally
	return t1.Score() < t2.Score()
}

// GetObjectKind returns a schema object.
func (Section) GetObjectKind() schema.ObjectKind {
	return nil
}

// DeepCopyObject returns a container copy.
func (s Section) DeepCopyObject() runtime.Object {
	return s
}

// MaxSeverity gather the max severity in a collection of issues.
func (s Section) MaxSeverity() config.Level {
	max := config.OkLevel
	for _, issues := range s.Outcome {
		m := issues.MaxSeverity()
		if m > max {
			max = m
		}
	}

	return max
}

// MaxSeverity gather the max severity in a collection of issues.
func (i Issues) MaxSeverity() config.Level {
	max := config.OkLevel
	for _, is := range i {
		if is.Level > max {
			max = is.Level
		}
	}

	return max
}

// CountSeverity counts severity level instances.
func (i Issues) CountSeverity(l config.Level) int {
	var count int
	for _, is := range i {
		if is.Level == l {
			count++
		}
	}

	return count
}
