package vpa

import (
	"encoding/json"
	"fmt"
	"net/http"
)

// Pod stores memory usage information of a pod.
type Pod struct {
	Name      string `json:"name,omitempty"`
	Namespace string `json:"namespace,omitempty"`
	Limit     int64  `json:"limit,omitempty"`
	MinLimit  int64  `json:"min_limit,omitempty"`
	MaxLimit  int64  `json:"max_limit,omitempty"`
	Used      int64  `json:"used,omitempty"`
}

func (p *Pod) key() string {
	return podKey(p.Namespace, p.Name)
}

func (m *MemoryAllocator) HandleReport(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close() //nolint:errcheck // ignore error
	var pod Pod
	if err := json.NewDecoder(r.Body).Decode(&pod); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	m.report(pod)
	w.WriteHeader(http.StatusOK)
}

// ScaleRequest is the request to scale a pod's memory limit.
type ScaleRequest struct {
	Namespace string `json:"namespace,omitempty"`
	Name      string `json:"name,omitempty"`
	Limit     int64  `json:"limit,omitempty"`
}

func (m *MemoryAllocator) HandleScale(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close() //nolint:errcheck // ignore error
	var req ScaleRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if !m.ready.Load() {
		http.Error(w, "not ready", http.StatusNotAcceptable)
		return
	}
	if err := m.scale(req.Namespace, req.Name, req.Limit); err != nil {
		http.Error(w, err.Error(), http.StatusNotAcceptable)
		return
	}
	w.WriteHeader(http.StatusOK)
}

type memoryAllocatorStatus struct {
	Capacity  string      `json:"capacity,omitempty"`
	Allocated string      `json:"allocated,omitempty"`
	Ready     bool        `json:"ready,omitempty"`
	Pods      []podStatus `json:"pods,omitempty"`
}

type podStatus struct {
	Name      string `json:"name,omitempty"`
	Namespace string `json:"namespace,omitempty"`
	Limit     string `json:"limit,omitempty"`
	MinLimit  string `json:"min_limit,omitempty"`
	MaxLimit  string `json:"max_limit,omitempty"`
	Used      string `json:"used,omitempty"`
	Allocated string `json:"allocated,omitempty"`
}

func (m *MemoryAllocator) HandleStatus(w http.ResponseWriter, r *http.Request) {
	m.RLock()
	defer m.RUnlock()

	status := m.dumpStatus()
	if err := json.NewEncoder(w).Encode(status); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (m *MemoryAllocator) dumpStatus() memoryAllocatorStatus {
	m.RLock()
	defer m.RUnlock()
	var status memoryAllocatorStatus
	status.Capacity = formatBytes(m.capacity)
	status.Allocated = formatBytes(m.allocated)
	status.Ready = m.ready.Load()
	for _, p := range m.pods {
		status.Pods = append(status.Pods, podStatus{
			Name:      p.name,
			Namespace: p.namespace,
			Limit:     formatBytes(p.limit),
			MinLimit:  formatBytes(p.minLimit),
			MaxLimit:  formatBytes(p.maxLimit),
			Used:      formatBytes(p.used.Load()),
			Allocated: formatBytes(p.limit - p.minLimit),
		})
	}
	return status
}

func formatBytes(b int64) string {
	const (
		_ = 1 << (10 * iota) //nolint: gomnd // ignore magic number
		KiB
		MiB
		GiB
		TiB
	)
	switch {
	case b >= TiB:
		return formatFloat(float64(b)/TiB) + "TiB"
	case b >= GiB:
		return formatFloat(float64(b)/GiB) + "GiB"
	case b >= MiB:
		return formatFloat(float64(b)/MiB) + "MiB"
	case b >= KiB:
		return formatFloat(float64(b)/KiB) + "KiB"
	default:
		return formatFloat(float64(b)) + "B"
	}
}

func formatFloat(f float64) string {
	if f < 10 { //nolint: gomnd // ignore magic number
		return fmt.Sprintf("%.2f", f)
	}
	return fmt.Sprintf("%.0f", f)
}
