package metrics

import (
	"context"
	"fmt"
	"log"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"gitea.dooplex.hu/admin/felhom-controller/internal/system"
)

// MetricsCollector periodically samples system and container metrics and stores them.
type MetricsCollector struct {
	store        *MetricsStore
	cpuCollector *system.CPUCollector
	hddPath      string
	logger       *log.Logger
	cancel       context.CancelFunc
}

// NewMetricsCollector creates a new collector.
func NewMetricsCollector(store *MetricsStore, cpuCollector *system.CPUCollector, hddPath string, logger *log.Logger) *MetricsCollector {
	return &MetricsCollector{
		store:        store,
		cpuCollector: cpuCollector,
		hddPath:      hddPath,
		logger:       logger,
	}
}

// Start begins the background collection loop (every 60 seconds).
func (c *MetricsCollector) Start(ctx context.Context) {
	ctx, c.cancel = context.WithCancel(ctx)
	go c.loop(ctx)
}

// Stop cancels the collection loop.
func (c *MetricsCollector) Stop() {
	if c.cancel != nil {
		c.cancel()
	}
}

func (c *MetricsCollector) loop(ctx context.Context) {
	ticker := time.NewTicker(60 * time.Second)
	defer ticker.Stop()

	// Sample immediately on start
	c.sample()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			c.sample()
		}
	}
}

func (c *MetricsCollector) sample() {
	sys := c.sampleSystem()
	if err := c.store.InsertSystemMetrics(sys); err != nil {
		c.logger.Printf("[WARN] Failed to store system metrics: %v", err)
	}

	containers := c.sampleContainers()
	if err := c.store.InsertContainerMetrics(containers); err != nil {
		c.logger.Printf("[WARN] Failed to store container metrics: %v", err)
	}
}

func (c *MetricsCollector) sampleSystem() SystemSample {
	info := system.GetInfo(c.hddPath, c.cpuCollector)
	return SystemSample{
		Timestamp:   time.Now().Unix(),
		CPUPercent:  info.CPUPercent,
		MemUsedMB:   int(info.UsedMemMB),
		MemTotalMB:  int(info.TotalMemMB),
		TempCelsius: info.TemperatureCelsius,
		LoadAvg1:    info.LoadAvg1,
		LoadAvg5:    info.LoadAvg5,
		LoadAvg15:   info.LoadAvg15,
		DiskUsedGB:  info.DiskUsedGB,
		DiskTotalGB: info.DiskTotalGB,
		HDDUsedGB:   info.HDDUsedGB,
		HDDTotalGB:  info.HDDTotalGB,
	}
}

func (c *MetricsCollector) sampleContainers() []ContainerSample {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	cmd := exec.CommandContext(ctx, "docker", "stats", "--no-stream",
		"--format", "{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}")
	out, err := cmd.Output()
	if err != nil {
		c.logger.Printf("[WARN] docker stats failed: %v", err)
		return nil
	}

	now := time.Now().Unix()
	var samples []ContainerSample

	for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
		if line == "" {
			continue
		}

		parts := strings.Split(line, "\t")
		if len(parts) < 5 {
			continue
		}

		name := parts[0]
		cpuPct := parsePercent(parts[1])
		memUsage, memLimit := parseMemUsage(parts[2])
		netRx, netTx := parseIOPair(parts[3])
		blkRead, blkWrite := parseIOPair(parts[4])

		samples = append(samples, ContainerSample{
			Timestamp:       now,
			ContainerName:   name,
			CPUPercent:      cpuPct,
			MemUsageMB:      memUsage,
			MemLimitMB:      memLimit,
			NetRxBytes:      netRx,
			NetTxBytes:      netTx,
			BlockReadBytes:  blkRead,
			BlockWriteBytes: blkWrite,
		})
	}

	return samples
}

// parsePercent parses "2.50%" → 2.50
func parsePercent(s string) float64 {
	s = strings.TrimSpace(s)
	s = strings.TrimSuffix(s, "%")
	v, _ := strconv.ParseFloat(s, 64)
	return v
}

// parseMemUsage parses "150.5MiB / 512MiB" → (150.5, 512.0)
func parseMemUsage(s string) (usage, limit float64) {
	parts := strings.Split(s, "/")
	if len(parts) != 2 {
		return 0, 0
	}
	usage = parseSizeToMB(strings.TrimSpace(parts[0]))
	limit = parseSizeToMB(strings.TrimSpace(parts[1]))
	return
}

// parseIOPair parses "1.5MB / 2.3MB" → (1500000, 2300000) as bytes
func parseIOPair(s string) (int64, int64) {
	parts := strings.Split(s, "/")
	if len(parts) != 2 {
		return 0, 0
	}
	return parseSizeToBytes(strings.TrimSpace(parts[0])), parseSizeToBytes(strings.TrimSpace(parts[1]))
}

// parseSizeToMB parses "150.5MiB", "1.5GiB", "500kB" → value in MB
func parseSizeToMB(s string) float64 {
	s = strings.TrimSpace(s)

	multipliers := []struct {
		suffix string
		factor float64
	}{
		{"GiB", 1024},
		{"MiB", 1},
		{"KiB", 1.0 / 1024},
		{"GB", 1000},
		{"MB", 1},
		{"KB", 1.0 / 1000},
		{"kB", 1.0 / 1000},
		{"B", 1.0 / (1024 * 1024)},
	}

	for _, m := range multipliers {
		if strings.HasSuffix(s, m.suffix) {
			numStr := strings.TrimSpace(strings.TrimSuffix(s, m.suffix))
			val, err := strconv.ParseFloat(numStr, 64)
			if err != nil {
				return 0
			}
			return val * m.factor
		}
	}

	// Fallback: try to parse as plain number
	val, _ := strconv.ParseFloat(s, 64)
	return val
}

// parseSizeToBytes parses "1.5MB", "500kB", "2.3GB" → bytes
func parseSizeToBytes(s string) int64 {
	s = strings.TrimSpace(s)

	multipliers := []struct {
		suffix string
		factor float64
	}{
		{"GiB", 1024 * 1024 * 1024},
		{"MiB", 1024 * 1024},
		{"KiB", 1024},
		{"GB", 1e9},
		{"MB", 1e6},
		{"KB", 1e3},
		{"kB", 1e3},
		{"B", 1},
	}

	for _, m := range multipliers {
		if strings.HasSuffix(s, m.suffix) {
			numStr := strings.TrimSpace(strings.TrimSuffix(s, m.suffix))
			val, err := strconv.ParseFloat(numStr, 64)
			if err != nil {
				return 0
			}
			return int64(val * m.factor)
		}
	}

	val, _ := strconv.ParseFloat(s, 64)
	return int64(val)
}

// FormatUptime formats seconds into "X nap, Y óra" style for Hungarian display.
func FormatUptime(seconds int64) string {
	days := seconds / 86400
	hours := (seconds % 86400) / 3600
	minutes := (seconds % 3600) / 60

	if days > 0 {
		return fmt.Sprintf("%d nap, %d óra", days, hours)
	}
	if hours > 0 {
		return fmt.Sprintf("%d óra, %d perc", hours, minutes)
	}
	return fmt.Sprintf("%d perc", minutes)
}
