package confmgr

import (
	"fmt"
	"sort"
)

// ChangeStatus classifies a single diff entry.
type ChangeStatus string

const (
	StatusAdded     ChangeStatus = "added"
	StatusRemoved   ChangeStatus = "removed"
	StatusChanged   ChangeStatus = "changed"
	StatusUnchanged ChangeStatus = "unchanged"
)

// SetDiffEntry is one parameter-level difference between two config sets.
type SetDiffEntry struct {
	Key    string       `json:"key"`
	Status ChangeStatus `json:"status"`
	Left   *Parameter   `json:"left,omitempty"`
	Right  *Parameter   `json:"right,omitempty"`
}

// InstanceDiffEntry is one value-level difference between two config instances.
type InstanceDiffEntry struct {
	Key    string       `json:"key"`
	Status ChangeStatus `json:"status"`
	Left   any          `json:"left,omitempty"`
	Right  any          `json:"right,omitempty"`
}

// DiffSets compares two config sets parameter-by-parameter (keyed by Key),
// returning entries sorted by key. Both sides are included so the frontend can
// render either unified or side-by-side.
func DiffSets(left, right ConfigSet) []SetDiffEntry {
	keys := map[string]bool{}
	li := map[string]Parameter{}
	ri := map[string]Parameter{}
	for _, p := range left.Parameters {
		li[p.Key] = p
		keys[p.Key] = true
	}
	for _, p := range right.Parameters {
		ri[p.Key] = p
		keys[p.Key] = true
	}
	out := make([]SetDiffEntry, 0, len(keys))
	for k := range keys {
		l, lok := li[k]
		r, rok := ri[k]
		e := SetDiffEntry{Key: k}
		switch {
		case lok && !rok:
			e.Status = StatusRemoved
			lp := l
			e.Left = &lp
		case !lok && rok:
			e.Status = StatusAdded
			rp := r
			e.Right = &rp
		default:
			lp, rp := l, r
			e.Left, e.Right = &lp, &rp
			if paramEqual(l, r) {
				e.Status = StatusUnchanged
			} else {
				e.Status = StatusChanged
			}
		}
		out = append(out, e)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
	return out
}

// DiffInstances compares two config instances value-by-value (keyed by
// parameter key), returning entries sorted by key.
func DiffInstances(left, right ConfigInstance) []InstanceDiffEntry {
	keys := map[string]bool{}
	for k := range left.Values {
		keys[k] = true
	}
	for k := range right.Values {
		keys[k] = true
	}
	out := make([]InstanceDiffEntry, 0, len(keys))
	for k := range keys {
		lv, lok := left.Values[k]
		rv, rok := right.Values[k]
		e := InstanceDiffEntry{Key: k, Left: lv, Right: rv}
		switch {
		case lok && !rok:
			e.Status = StatusRemoved
		case !lok && rok:
			e.Status = StatusAdded
		case valueEqual(lv, rv):
			e.Status = StatusUnchanged
		default:
			e.Status = StatusChanged
		}
		out = append(out, e)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
	return out
}

func paramEqual(a, b Parameter) bool {
	if a.Label != b.Label || a.Group != b.Group || a.Subgroup != b.Subgroup ||
		a.DS != b.DS || a.Signal != b.Signal || a.Type != b.Type ||
		a.Mandatory != b.Mandatory || a.Unit != b.Unit || a.Description != b.Description {
		return false
	}
	if !valueEqual(a.Default, b.Default) || !floatPtrEqual(a.Min, b.Min) || !floatPtrEqual(a.Max, b.Max) {
		return false
	}
	if len(a.EnumValues) != len(b.EnumValues) {
		return false
	}
	for i := range a.EnumValues {
		if a.EnumValues[i] != b.EnumValues[i] {
			return false
		}
	}
	return true
}

func floatPtrEqual(a, b *float64) bool {
	if a == nil || b == nil {
		return a == b
	}
	return *a == *b
}

// valueEqual compares two JSON-decoded scalar values for diff purposes by
// rendering them to a canonical string.
func valueEqual(a, b any) bool {
	return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
}
