package metrics

import (
	"bufio"
	"fmt"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

type Snapshot struct {
	CPUPercent float64 `json:"cpuPercent"`
	MemBytes   int64   `json:"memBytes"`
	MemLimit   int64   `json:"memLimit"`
}

type Collector struct {
	mu            sync.Mutex
	lastCPUUseUse uint64
	lastTime      time.Time
}

func (c *Collector) Snapshot() Snapshot {
	c.mu.Lock()
	defer c.mu.Unlock()

	usage, okCPU := readCPUUsage()
	mem, limit, okMem := readMemoryUsage()

	cpuPercent := 0.0
	if okCPU {
		now := time.Now()
		if !c.lastTime.IsZero() {
			deltaUsage := float64(usage - c.lastCPUUseUse)
			deltaTime := now.Sub(c.lastTime).Seconds()
			cpuQuota := cpuQuotaCount()
			if cpuQuota <= 0 {
				cpuQuota = float64(runtime.NumCPU())
			}
			if deltaTime > 0 && cpuQuota > 0 {
				cpuPercent = (deltaUsage / (deltaTime * 1_000_000)) * (100 / cpuQuota)
			}
		}
		c.lastCPUUseUse = usage
		c.lastTime = now
	}

	if !okMem || mem <= 0 {
		var memStats runtime.MemStats
		runtime.ReadMemStats(&memStats)
		mem = int64(memStats.Alloc)
		limit = int64(memStats.Sys)
	}

	return Snapshot{
		CPUPercent: cpuPercent,
		MemBytes:   mem,
		MemLimit:   limit,
	}
}

func readCPUUsage() (uint64, bool) {
	file, err := os.Open("/sys/fs/cgroup/cpu.stat")
	if err != nil {
		return readCPUUsageV1()
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		parts := strings.Fields(line)
		if len(parts) == 2 && parts[0] == "usage_usec" {
			val, err := strconv.ParseUint(parts[1], 10, 64)
			if err == nil {
				return val, true
			}
		}
	}
	return readCPUUsageV1()
}

func readMemoryUsage() (int64, int64, bool) {
	current, err := os.ReadFile("/sys/fs/cgroup/memory.current")
	if err != nil {
		return readMemoryUsageV1()
	}
	limit, err := os.ReadFile("/sys/fs/cgroup/memory.max")
	if err != nil {
		return readMemoryUsageV1()
	}
	memBytes, err := strconv.ParseInt(strings.TrimSpace(string(current)), 10, 64)
	if err != nil {
		return 0, 0, false
	}
	limitRaw := strings.TrimSpace(string(limit))
	if limitRaw == "max" {
		return memBytes, 0, true
	}
	limitBytes, err := strconv.ParseInt(limitRaw, 10, 64)
	if err != nil {
		return memBytes, 0, true
	}
	return memBytes, limitBytes, true
}

func readCPUUsageV1() (uint64, bool) {
	data, err := os.ReadFile("/sys/fs/cgroup/cpuacct/cpuacct.usage")
	if err != nil {
		return readProcCPUUsage()
	}
	val, err := strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
	if err != nil {
		return readProcCPUUsage()
	}
	return val / 1000, true
}

func readMemoryUsageV1() (int64, int64, bool) {
	current, err := os.ReadFile("/sys/fs/cgroup/memory/memory.usage_in_bytes")
	if err != nil {
		return 0, 0, false
	}
	limit, err := os.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes")
	if err != nil {
		return 0, 0, false
	}
	memBytes, err := strconv.ParseInt(strings.TrimSpace(string(current)), 10, 64)
	if err != nil {
		return 0, 0, false
	}
	limitBytes, err := strconv.ParseInt(strings.TrimSpace(string(limit)), 10, 64)
	if err != nil {
		return memBytes, 0, true
	}
	return memBytes, limitBytes, true
}

func readProcCPUUsage() (uint64, bool) {
	data, err := os.ReadFile("/proc/self/stat")
	if err != nil {
		return 0, false
	}
	contents := string(data)
	closeIdx := strings.LastIndex(contents, ")")
	if closeIdx == -1 || closeIdx+2 >= len(contents) {
		return 0, false
	}
	fields := strings.Fields(contents[closeIdx+2:])
	if len(fields) < 15 {
		return 0, false
	}
	utime, err := strconv.ParseUint(fields[11], 10, 64)
	if err != nil {
		return 0, false
	}
	stime, err := strconv.ParseUint(fields[12], 10, 64)
	if err != nil {
		return 0, false
	}
	clkTck := uint64(100)
	totalTicks := utime + stime
	usec := (totalTicks * 1_000_000) / clkTck
	return usec, true
}

func cpuQuotaCount() float64 {
	data, err := os.ReadFile("/sys/fs/cgroup/cpu.max")
	if err != nil {
		return float64(runtime.NumCPU())
	}
	parts := strings.Fields(string(data))
	if len(parts) != 2 {
		return 0
	}
	if parts[0] == "max" {
		return float64(runtime.NumCPU())
	}
	quota, err := strconv.ParseFloat(parts[0], 64)
	if err != nil {
		return 0
	}
	period, err := strconv.ParseFloat(parts[1], 64)
	if err != nil || period == 0 {
		return 0
	}
	return quota / period
}

func FormatBytes(size int64) string {
	if size < 1024 {
		return fmt.Sprintf("%d B", size)
	}
	kb := float64(size) / 1024
	if kb < 1024 {
		return fmt.Sprintf("%.1f KB", kb)
	}
	mb := kb / 1024
	if mb < 1024 {
		return fmt.Sprintf("%.1f MB", mb)
	}
	gb := mb / 1024
	return fmt.Sprintf("%.2f GB", gb)
}
