//nolint:mnd
package main

import (
	"context"
	"fmt"
	"image/color"
	"log"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"

	"git.eeqj.de/sneak/hdmistat/internal/fbdraw"
	"git.eeqj.de/sneak/hdmistat/internal/layout"
	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/disk"
	"github.com/shirou/gopsutil/v3/host"
	"github.com/shirou/gopsutil/v3/mem"
)

const (
	// DefaultFontSize is the font size used throughout the application
	DefaultFontSize = 24
)

// SimpleStatScreen implements the FrameGenerator interface
type SimpleStatScreen struct {
	width  int
	height int
}

// NewSimpleStatScreen creates a new simple stat screen
func NewSimpleStatScreen() *SimpleStatScreen {
	return &SimpleStatScreen{}
}

// Init initializes the screen with the display dimensions
func (s *SimpleStatScreen) Init(width, height int) error {
	s.width = width
	s.height = height
	return nil
}

// getSystemUptime returns the system uptime
func getSystemUptime() (time.Duration, error) {
	data, err := os.ReadFile("/proc/uptime")
	if err != nil {
		return 0, err
	}

	fields := strings.Fields(string(data))
	if len(fields) < 1 {
		return 0, fmt.Errorf("invalid /proc/uptime format")
	}

	seconds, err := strconv.ParseFloat(fields[0], 64)
	if err != nil {
		return 0, err
	}

	return time.Duration(seconds * float64(time.Second)), nil
}

// GenerateFrame generates a frame with system stats
func (s *SimpleStatScreen) GenerateFrame(grid *fbdraw.CharGrid) error {
	// Create a draw context that works directly on the provided grid
	draw := layout.NewDraw(grid)

	// Clear the screen with a dark background
	draw.Clear()

	// Get hostname
	hostname, _ := os.Hostname()

	// Title - moved down one line
	draw.Color(layout.Color("cyan")).Bold()
	draw.TextCenter(0, 3, "%s: info", hostname)
	draw.Plain()

	// Uptime
	uptime, err := getSystemUptime()
	if err != nil {
		uptime = 0
	}
	draw.Color(layout.Color("gray60"))
	draw.TextCenter(0, 4, "Uptime: %s", uptime.Round(time.Second).String())

	// Starting Y position for stats (with extra blank line after uptime)
	statY := 7

	// Calculate alignment positions
	labelX := 10
	valueX := 30   // For right-aligned values before the bar
	barX := 40     // Start of progress bars
	barWidth := 50 // Extended bar width

	// CPU Usage
	cpuPercent, _ := cpu.Percent(time.Millisecond*100, false)
	cpuUsage := 0.0
	if len(cpuPercent) > 0 {
		cpuUsage = cpuPercent[0]
	}
	draw.Color(layout.Color("white"))
	draw.Text(labelX, statY, "CPU Usage:")
	draw.Text(valueX-6, statY, "%6.1f%%", cpuUsage)
	barGrid := draw.Grid(barX, statY, barWidth+1, 1)
	barGrid.Bar(0, 0, barWidth, cpuUsage, getCPUColor(cpuUsage))
	// Add CPU details below
	draw.Color(layout.Color("gray60"))
	cpuInfo := getCPUInfo()
	draw.Text(labelX+2, statY+1, "%s", cpuInfo)

	// Memory Usage
	statY += 4 // Add extra blank line (3 + 1 for the CPU detail line)
	vmStat, _ := mem.VirtualMemory()
	memUsage := vmStat.UsedPercent
	draw.Color(layout.Color("white"))
	draw.Text(labelX, statY, "Memory:")
	draw.Text(valueX-6, statY, "%6.1f%%", memUsage)
	barGrid = draw.Grid(barX, statY, barWidth+1, 1)
	barGrid.Bar(0, 0, barWidth, memUsage, getMemoryColor(memUsage))
	// Add usage details below
	draw.Color(layout.Color("gray60"))
	draw.Text(
		labelX+2,
		statY+1,
		"%s / %s",
		layout.Bytes(vmStat.Used),
		layout.Bytes(vmStat.Total),
	)

	// System Temperature (only if available)
	temp := getSystemTemperature()
	if temp > 0 {
		statY += 4                            // Add blank line (3 + 1 for the memory detail line)
		tempPercent := (temp / 100.0) * 100.0 // Assume 100°C as max
		if tempPercent > 100 {
			tempPercent = 100
		}
		draw.Color(layout.Color("white"))
		draw.Text(labelX, statY, "Temperature:")
		draw.Text(valueX-8, statY, "%6.1f°C", temp)
		barGrid = draw.Grid(barX, statY, barWidth+1, 1)
		barGrid.Bar(0, 0, barWidth, tempPercent, getTempColor(temp))
	}

	// Filesystem Usage
	if temp > 0 {
		statY += 3 // Add blank line after temperature
	} else {
		statY += 4 // Add blank line after memory (3 + 1 for the memory detail line)
	}
	fsPath, fsUsage, fsUsed, fsTotal := getLargestFilesystemUsageWithDetails()
	draw.Color(layout.Color("white"))
	draw.Text(labelX, statY, "Filesystem:")
	draw.Text(valueX-6, statY, "%6.1f%%", fsUsage)
	barGrid = draw.Grid(barX, statY, barWidth+1, 1)
	barGrid.Bar(0, 0, barWidth, fsUsage, getFSColor(fsUsage))
	// Add filesystem details below
	draw.Color(layout.Color("gray60"))
	draw.Text(
		labelX+2,
		statY+1,
		"%s: %s / %s",
		fsPath,
		layout.Bytes(fsUsed),
		layout.Bytes(fsTotal),
	)

	// Add a decorative border
	borderGrid := draw.Grid(2, 2, grid.Width-4, grid.Height-4)
	borderGrid.Border(layout.Color("gray30"))

	return nil
}

// FramesPerSecond returns the desired frame rate (1 FPS for stat updates)
func (s *SimpleStatScreen) FramesPerSecond() float64 {
	return 1.0
}

// getCPUColor returns a color based on CPU usage
func getCPUColor(percent float64) color.Color {
	if percent > 80 {
		return layout.Color("red")
	} else if percent > 50 {
		return layout.Color("yellow")
	}
	return layout.Color("green")
}

// getMemoryColor returns a color based on memory usage
func getMemoryColor(percent float64) color.Color {
	if percent > 90 {
		return layout.Color("red")
	} else if percent > 70 {
		return layout.Color("yellow")
	}
	return layout.Color("green")
}

// getTempColor returns a color based on temperature
func getTempColor(temp float64) color.Color {
	if temp > 80 {
		return layout.Color("red")
	} else if temp > 60 {
		return layout.Color("yellow")
	}
	return layout.Color("green")
}

// getFSColor returns a color based on filesystem usage
func getFSColor(percent float64) color.Color {
	if percent > 90 {
		return layout.Color("red")
	} else if percent > 80 {
		return layout.Color("yellow")
	}
	return layout.Color("green")
}

// getCPUInfo returns CPU details like cores, threads, and clock speed
func getCPUInfo() string {
	// Get CPU info
	cpuInfos, err := cpu.Info()
	if err != nil || len(cpuInfos) == 0 {
		return "Unknown CPU"
	}

	// Get logical (threads) and physical core counts
	logical, _ := cpu.Counts(true)
	physical, _ := cpu.Counts(false)

	// Get frequency info
	freq := cpuInfos[0].Mhz
	freqStr := ""
	if freq > 0 {
		if freq >= 1000 {
			freqStr = fmt.Sprintf("%.2f GHz", freq/1000.0)
		} else {
			freqStr = fmt.Sprintf("%.0f MHz", freq)
		}
		return fmt.Sprintf(
			"%d cores, %d threads @ %s",
			physical,
			logical,
			freqStr,
		)
	}

	return fmt.Sprintf("%d cores, %d threads", physical, logical)
}

// getSystemTemperature returns the system temperature in Celsius
func getSystemTemperature() float64 {
	// Try to get temperature from host sensors
	temps, err := host.SensorsTemperatures()
	if err != nil || len(temps) == 0 {
		return 0.0
	}

	// Find the highest temperature
	maxTemp := 0.0
	for _, temp := range temps {
		if temp.Temperature > maxTemp {
			maxTemp = temp.Temperature
		}
	}

	return maxTemp
}

// getLargestFilesystemUsageWithDetails returns the path, usage percentage, used bytes, and total bytes
func getLargestFilesystemUsageWithDetails() (string, float64, uint64, uint64) {
	partitions, err := disk.Partitions(false)
	if err != nil {
		return "/", 0.0, 0, 0
	}

	var largestPath string
	var largestSize uint64
	var largestUsage float64
	var largestUsed uint64

	for _, partition := range partitions {
		// Skip special filesystems
		if strings.HasPrefix(partition.Mountpoint, "/proc") ||
			strings.HasPrefix(partition.Mountpoint, "/sys") ||
			strings.HasPrefix(partition.Mountpoint, "/dev") ||
			strings.HasPrefix(partition.Mountpoint, "/run") ||
			partition.Fstype == "tmpfs" ||
			partition.Fstype == "devtmpfs" {
			continue
		}

		usage, err := disk.Usage(partition.Mountpoint)
		if err != nil {
			continue
		}

		// Find the filesystem with the largest total size
		if usage.Total > largestSize {
			largestSize = usage.Total
			largestUsage = usage.UsedPercent
			largestUsed = usage.Used
			largestPath = partition.Mountpoint
		}
	}

	if largestPath == "" {
		return "/", 0.0, 0, 0
	}

	return largestPath, largestUsage, largestUsed, largestSize
}

func main() {
	// Set up signal handling
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		<-sigChan
		log.Println("Received shutdown signal")
		cancel()
	}()

	// Create framebuffer display
	display, err := fbdraw.NewFBDisplayAuto()
	if err != nil {
		log.Fatalf("Failed to open framebuffer: %v", err)
	}
	defer func() {
		if err := display.Close(); err != nil {
			log.Printf("Failed to close display: %v", err)
		}
	}()

	// Create carousel with no rotation (single screen)
	carousel := fbdraw.NewCarousel(display, 0) // 0 means no rotation

	// Set font size
	if err := carousel.SetFontSize(DefaultFontSize); err != nil {
		log.Fatalf("Failed to set font size: %v", err)
	}

	// Add our simple stat screen with header
	statScreen := NewSimpleStatScreen()
	wrappedScreen := fbdraw.NewHeaderWrapper(statScreen)

	if err := carousel.AddScreen("Simple Stats", wrappedScreen); err != nil {
		log.Fatalf("Failed to add screen: %v", err)
	}

	// Run the carousel
	log.Println("Starting fbsimplestat...")
	log.Println("Press Ctrl+C to exit")

	// Run carousel in a goroutine
	done := make(chan error, 1)
	go func() {
		done <- carousel.Run()
	}()

	// Wait for either context cancellation or carousel to finish
	select {
	case <-ctx.Done():
		log.Println("Stopping carousel...")
		carousel.Stop()
		<-done // Wait for carousel to finish
	case err := <-done:
		if err != nil {
			log.Printf("Carousel error: %v", err)
		}
	}

	log.Println("fbsimplestat exited cleanly")
}
