package cpu

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"strings"
)

// CPUInfo holds information about the system CPU
type CPUInfo struct {
	LogicalCores  int      `json:"logical_cores"`
	PhysicalCores int      `json:"physical_cores"`
	Architecture  string   `json:"architecture"`
	ModelName     string   `json:"model_name"`
	MaxFrequency  float64  `json:"max_frequency_mhz"`
	CacheSize     string   `json:"cache_size"`
	Vendor        string   `json:"vendor"`
	Features      []string `json:"features"`
}

// Detector provides CPU detection functionality
type Detector struct {
	info *CPUInfo
}

// NewDetector creates a new CPU detector
func NewDetector() *Detector {
	return &Detector{}
}

// DetectCPU detects CPU information for the current system
func (d *Detector) DetectCPU() (*CPUInfo, error) {
	if d.info != nil {
		return d.info, nil
	}

	info := &CPUInfo{
		LogicalCores: runtime.NumCPU(),
		Architecture: runtime.GOARCH,
	}

	// Platform-specific detection
	switch runtime.GOOS {
	case "linux":
		if err := d.detectLinux(info); err != nil {
			return info, fmt.Errorf("linux CPU detection failed: %w", err)
		}
	case "darwin":
		if err := d.detectDarwin(info); err != nil {
			return info, fmt.Errorf("darwin CPU detection failed: %w", err)
		}
	case "windows":
		if err := d.detectWindows(info); err != nil {
			return info, fmt.Errorf("windows CPU detection failed: %w", err)
		}
	default:
		// Fallback for unsupported platforms
		info.PhysicalCores = info.LogicalCores
		info.ModelName = "Unknown"
		info.Vendor = "Unknown"
	}

	d.info = info
	return info, nil
}

// detectLinux detects CPU information on Linux systems
func (d *Detector) detectLinux(info *CPUInfo) error {
	file, err := os.Open("/proc/cpuinfo")
	if err != nil {
		return err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	physicalCoreCount := make(map[string]bool)

	for scanner.Scan() {
		line := scanner.Text()
		if strings.TrimSpace(line) == "" {
			continue
		}

		parts := strings.SplitN(line, ":", 2)
		if len(parts) != 2 {
			continue
		}

		key := strings.TrimSpace(parts[0])
		value := strings.TrimSpace(parts[1])

		switch key {
		case "model name":
			if info.ModelName == "" {
				info.ModelName = value
			}
		case "vendor_id":
			if info.Vendor == "" {
				info.Vendor = value
			}
		case "cpu MHz":
			if freq, err := strconv.ParseFloat(value, 64); err == nil && info.MaxFrequency < freq {
				info.MaxFrequency = freq
			}
		case "cache size":
			if info.CacheSize == "" {
				info.CacheSize = value
			}
		case "flags", "Features":
			if len(info.Features) == 0 {
				info.Features = strings.Fields(value)
			}
		case "physical id":
			physicalCoreCount[value] = true
		}
	}

	// Calculate physical cores
	if len(physicalCoreCount) > 0 {
		info.PhysicalCores = len(physicalCoreCount)
	} else {
		// Fallback: assume hyperthreading if logical > 1
		info.PhysicalCores = info.LogicalCores
		if info.LogicalCores > 1 {
			info.PhysicalCores = info.LogicalCores / 2
		}
	}

	// Try to get more accurate physical core count from lscpu
	if cmd := exec.Command("lscpu"); cmd != nil {
		if output, err := cmd.Output(); err == nil {
			d.parseLscpu(string(output), info)
		}
	}

	return scanner.Err()
}

// parseLscpu parses lscpu output for more accurate CPU information
func (d *Detector) parseLscpu(output string, info *CPUInfo) {
	lines := strings.Split(output, "\n")
	for _, line := range lines {
		if strings.TrimSpace(line) == "" {
			continue
		}

		parts := strings.SplitN(line, ":", 2)
		if len(parts) != 2 {
			continue
		}

		key := strings.TrimSpace(parts[0])
		value := strings.TrimSpace(parts[1])

		switch key {
		case "Core(s) per socket":
			if cores, err := strconv.Atoi(value); err == nil {
				if sockets := d.getSocketCount(output); sockets > 0 {
					info.PhysicalCores = cores * sockets
				}
			}
		case "Model name":
			info.ModelName = value
		case "Vendor ID":
			info.Vendor = value
		case "CPU max MHz":
			if freq, err := strconv.ParseFloat(value, 64); err == nil {
				info.MaxFrequency = freq
			}
		}
	}
}

// getSocketCount extracts socket count from lscpu output
func (d *Detector) getSocketCount(output string) int {
	lines := strings.Split(output, "\n")
	for _, line := range lines {
		if strings.Contains(line, "Socket(s):") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				if sockets, err := strconv.Atoi(strings.TrimSpace(parts[1])); err == nil {
					return sockets
				}
			}
		}
	}
	return 1 // Default to 1 socket
}

// detectDarwin detects CPU information on macOS systems
func (d *Detector) detectDarwin(info *CPUInfo) error {
	// Get CPU brand
	if output, err := exec.Command("sysctl", "-n", "machdep.cpu.brand_string").Output(); err == nil {
		info.ModelName = strings.TrimSpace(string(output))
	}

	// Get physical cores
	if output, err := exec.Command("sysctl", "-n", "hw.physicalcpu").Output(); err == nil {
		if cores, err := strconv.Atoi(strings.TrimSpace(string(output))); err == nil {
			info.PhysicalCores = cores
		}
	}

	// Get max frequency
	if output, err := exec.Command("sysctl", "-n", "hw.cpufrequency_max").Output(); err == nil {
		if freq, err := strconv.ParseFloat(strings.TrimSpace(string(output)), 64); err == nil {
			info.MaxFrequency = freq / 1000000 // Convert Hz to MHz
		}
	}

	// Get vendor
	if output, err := exec.Command("sysctl", "-n", "machdep.cpu.vendor").Output(); err == nil {
		info.Vendor = strings.TrimSpace(string(output))
	}

	// Get cache size
	if output, err := exec.Command("sysctl", "-n", "hw.l3cachesize").Output(); err == nil {
		if cache, err := strconv.Atoi(strings.TrimSpace(string(output))); err == nil {
			info.CacheSize = fmt.Sprintf("%d KB", cache/1024)
		}
	}

	return nil
}

// detectWindows detects CPU information on Windows systems
func (d *Detector) detectWindows(info *CPUInfo) error {
	// Use wmic to get CPU information
	cmd := exec.Command("wmic", "cpu", "get", "Name,NumberOfCores,NumberOfLogicalProcessors,MaxClockSpeed", "/format:list")
	output, err := cmd.Output()
	if err != nil {
		return err
	}

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

		parts := strings.SplitN(line, "=", 2)
		if len(parts) != 2 {
			continue
		}

		key := strings.TrimSpace(parts[0])
		value := strings.TrimSpace(parts[1])

		switch key {
		case "Name":
			if value != "" {
				info.ModelName = value
			}
		case "NumberOfCores":
			if cores, err := strconv.Atoi(value); err == nil {
				info.PhysicalCores = cores
			}
		case "MaxClockSpeed":
			if freq, err := strconv.ParseFloat(value, 64); err == nil {
				info.MaxFrequency = freq
			}
		}
	}

	// Get vendor information
	cmd = exec.Command("wmic", "cpu", "get", "Manufacturer", "/format:list")
	if output, err := cmd.Output(); err == nil {
		lines := strings.Split(string(output), "\n")
		for _, line := range lines {
			if strings.HasPrefix(line, "Manufacturer=") {
				info.Vendor = strings.TrimSpace(strings.SplitN(line, "=", 2)[1])
				break
			}
		}
	}

	return nil
}

// CalculateOptimalJobs calculates optimal job count based on CPU info and workload type
func (d *Detector) CalculateOptimalJobs(workloadType string, maxJobs int) (int, error) {
	info, err := d.DetectCPU()
	if err != nil {
		return 1, err
	}

	var optimal int

	switch workloadType {
	case "cpu-intensive":
		// For CPU-intensive tasks, use physical cores
		optimal = info.PhysicalCores
	case "io-intensive":
		// For I/O intensive tasks, can use more jobs than cores
		optimal = info.LogicalCores * 2
	case "balanced":
		// Balanced workload, use logical cores
		optimal = info.LogicalCores
	default:
		optimal = info.LogicalCores
	}

	// Apply safety limits
	if optimal < 1 {
		optimal = 1
	}
	if maxJobs > 0 && optimal > maxJobs {
		optimal = maxJobs
	}

	return optimal, nil
}

// GetCPUInfo returns the detected CPU information
func (d *Detector) GetCPUInfo() *CPUInfo {
	return d.info
}

// FormatCPUInfo returns a formatted string representation of CPU info
func (info *CPUInfo) FormatCPUInfo() string {
	var sb strings.Builder

	sb.WriteString(fmt.Sprintf("Architecture: %s\n", info.Architecture))
	sb.WriteString(fmt.Sprintf("Logical Cores: %d\n", info.LogicalCores))
	sb.WriteString(fmt.Sprintf("Physical Cores: %d\n", info.PhysicalCores))

	if info.ModelName != "" {
		sb.WriteString(fmt.Sprintf("Model: %s\n", info.ModelName))
	}
	if info.Vendor != "" {
		sb.WriteString(fmt.Sprintf("Vendor: %s\n", info.Vendor))
	}
	if info.MaxFrequency > 0 {
		sb.WriteString(fmt.Sprintf("Max Frequency: %.2f MHz\n", info.MaxFrequency))
	}
	if info.CacheSize != "" {
		sb.WriteString(fmt.Sprintf("Cache Size: %s\n", info.CacheSize))
	}

	return sb.String()
}
