package docker

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/moby/moby/api/types/container"
	"github.com/moby/moby/client"
)

// ContainerStats holds computed CPU, memory, network, and block I/O
// usage for a container. Network and block I/O values are cumulative
// byte counters — compute rates by differencing two samples.
type ContainerStats struct {
	Timestamp       time.Time `json:"timestamp"`
	CPUPercent      float64   `json:"cpu_percent"`
	MemoryUsage     int64     `json:"memory_usage"`
	MemoryLimit     int64     `json:"memory_limit"`
	MemoryPercent   float64   `json:"memory_percent"`
	NetworkRxBytes  int64     `json:"network_rx_bytes"`
	NetworkTxBytes  int64     `json:"network_tx_bytes"`
	BlockReadBytes  int64     `json:"block_read_bytes"`
	BlockWriteBytes int64     `json:"block_write_bytes"`
}

// GetContainerStats retrieves a one-shot stats snapshot for the given container
// and computes CPU, memory, network, and block I/O metrics.
func (c *Client) GetContainerStats(ctx context.Context, containerID string) (ContainerStats, error) {
	result, err := c.api.ContainerStats(ctx, containerID, client.ContainerStatsOptions{
		Stream:                false,
		IncludePreviousSample: true,
	})
	if err != nil {
		return ContainerStats{}, fmt.Errorf("get container stats %s: %w", containerID, err)
	}
	defer result.Body.Close()

	var stats container.StatsResponse
	if err := json.NewDecoder(result.Body).Decode(&stats); err != nil {
		return ContainerStats{}, fmt.Errorf("decode container stats %s: %w", containerID, err)
	}

	cpuPercent := calculateCPUPercent(stats)
	memUsage := int64(stats.MemoryStats.Usage)
	memLimit := int64(stats.MemoryStats.Limit)
	var memPercent float64
	if memLimit > 0 {
		memPercent = float64(memUsage) / float64(memLimit) * 100.0
	}

	rxBytes, txBytes := sumNetworkBytes(stats.Networks)
	readBytes, writeBytes := sumBlockIOBytes(stats.BlkioStats.IoServiceBytesRecursive)

	ts := stats.Read
	if ts.IsZero() {
		ts = time.Now().UTC()
	}

	return ContainerStats{
		Timestamp:       ts,
		CPUPercent:      cpuPercent,
		MemoryUsage:     memUsage,
		MemoryLimit:     memLimit,
		MemoryPercent:   memPercent,
		NetworkRxBytes:  rxBytes,
		NetworkTxBytes:  txBytes,
		BlockReadBytes:  readBytes,
		BlockWriteBytes: writeBytes,
	}, nil
}

// sumNetworkBytes aggregates rx/tx byte counters across all network interfaces
// for a single container. Missing Networks map (disabled networking) yields zeros.
func sumNetworkBytes(nets map[string]container.NetworkStats) (rx, tx int64) {
	for _, n := range nets {
		rx += int64(n.RxBytes)
		tx += int64(n.TxBytes)
	}
	return rx, tx
}

// sumBlockIOBytes totals read/write bytes across all block devices from the
// cgroup io_service_bytes_recursive entries. The "Op" field is "read"/"write"
// on cgroup v2 and "Read"/"Write" on cgroup v1 — match case-insensitively so
// either runtime works.
func sumBlockIOBytes(entries []container.BlkioStatEntry) (read, write int64) {
	for _, e := range entries {
		switch {
		case strings.EqualFold(e.Op, "read"):
			read += int64(e.Value)
		case strings.EqualFold(e.Op, "write"):
			write += int64(e.Value)
		}
	}
	return read, write
}

// calculateCPUPercent computes CPU usage percentage from a stats response
// using the delta between current and previous CPU readings.
func calculateCPUPercent(stats container.StatsResponse) float64 {
	cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage)
	systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage)

	if systemDelta <= 0 || cpuDelta < 0 {
		return 0.0
	}

	onlineCPUs := float64(stats.CPUStats.OnlineCPUs)
	if onlineCPUs == 0 {
		onlineCPUs = 1
	}

	return (cpuDelta / systemDelta) * onlineCPUs * 100.0
}
