//go:build !remote

package abi

import (
	"context"
	"errors"
	"fmt"
	"strconv"

	"github.com/containers/podman/v5/libpod"
	"github.com/containers/podman/v5/libpod/define"
	"github.com/containers/podman/v5/pkg/domain/entities"
	"github.com/containers/podman/v5/pkg/rootless"
	"github.com/docker/go-units"
	"go.podman.io/common/pkg/cgroups"
)

// PodStats implements printing stats about pods.
func (ic *ContainerEngine) PodStats(_ context.Context, namesOrIds []string, options entities.PodStatsOptions) ([]*entities.PodStatsReport, error) {
	// Cgroups v2 check for rootless.
	if rootless.IsRootless() {
		unified, err := cgroups.IsCgroup2UnifiedMode()
		if err != nil {
			return nil, err
		}
		if !unified {
			return nil, errors.New("pod stats is not supported in rootless mode without cgroups v2")
		}
	}
	// Get the (running) pods and convert them to the entities format.
	pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod)
	if err != nil {
		return nil, fmt.Errorf("unable to get list of pods: %w", err)
	}
	return ic.podsToStatsReport(pods)
}

// podsToStatsReport converts a slice of pods into a corresponding slice of stats reports.
func (ic *ContainerEngine) podsToStatsReport(pods []*libpod.Pod) ([]*entities.PodStatsReport, error) {
	reports := []*entities.PodStatsReport{}
	for i := range pods { // Access by index to prevent potential loop-variable leaks.
		podStats, err := pods[i].GetPodStats()
		if err != nil {
			// pod was removed, skip it
			if errors.Is(err, define.ErrNoSuchPod) {
				continue
			}
			return nil, err
		}
		podID := pods[i].ID()[:12]
		for j := range podStats {
			var podNetInput uint64
			var podNetOutput uint64
			for _, stats := range podStats[j].Network {
				podNetInput += stats.RxBytes
				podNetOutput += stats.TxBytes
			}

			r := entities.PodStatsReport{
				CPU:           floatToPercentString(podStats[j].CPU),
				MemUsage:      combineHumanValues(podStats[j].MemUsage, podStats[j].MemLimit),
				MemUsageBytes: combineBytesValues(podStats[j].MemUsage, podStats[j].MemLimit),
				Mem:           floatToPercentString(podStats[j].MemPerc),
				NetIO:         combineHumanValues(podNetInput, podNetOutput),
				BlockIO:       combineHumanValues(podStats[j].BlockInput, podStats[j].BlockOutput),
				PIDS:          pidsToString(podStats[j].PIDs),
				CID:           podStats[j].ContainerID[:12],
				Name:          podStats[j].Name,
				Pod:           podID,
			}
			reports = append(reports, &r)
		}
	}

	return reports, nil
}

func combineHumanValues(a, b uint64) string {
	if a == 0 && b == 0 {
		return "-- / --"
	}
	return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b)))
}

func combineBytesValues(a, b uint64) string {
	if a == 0 && b == 0 {
		return "-- / --"
	}
	return fmt.Sprintf("%s / %s", units.BytesSize(float64(a)), units.BytesSize(float64(b)))
}

func floatToPercentString(f float64) string {
	return fmt.Sprintf("%.2f%%", f)
}

func pidsToString(pid uint64) string {
	if pid == 0 {
		// If things go bazinga, return a safe value
		return "--"
	}
	return strconv.FormatUint(pid, 10)
}
