package system

import (
	"context"
	"fmt"
	"runtime"
	"strings"

	"git.gostacks.org/iwasforcedtobehere/WiseTLP/autotlp/pkg/types"
	"git.gostacks.org/iwasforcedtobehere/WiseTLP/autotlp/pkg/utils"
)

type Info struct {
	Distribution   string
	Version        string
	Architecture   string
	PackageManager string
}

func DetectSystem(ctx context.Context) (*Info, error) {
	if runtime.GOOS != "linux" {
		return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
	}

	info := &Info{
		Architecture: runtime.GOARCH,
	}

	if err := detectFromOSRelease(info); err != nil {
		if err := detectFromLSBRelease(info); err != nil {
			return nil, fmt.Errorf("failed to detect Linux distribution: %w", err)
		}
	}

	info.PackageManager = detectPackageManager(info.Distribution)

	return info, nil
}

func detectFromOSRelease(info *Info) error {
	const osReleasePath = "/etc/os-release"

	if !utils.FileExists(osReleasePath) {
		return fmt.Errorf("os-release file not found")
	}

	lines, err := utils.ReadFileLines(osReleasePath)
	if err != nil {
		return fmt.Errorf("failed to read os-release: %w", err)
	}

	osInfo := make(map[string]string)
	for _, line := range lines {
		if key, value, ok := utils.ParseKeyValue(line); ok {
			osInfo[key] = value
		}
	}

	if id, exists := osInfo["ID"]; exists {
		info.Distribution = id
	}

	if version, exists := osInfo["VERSION_ID"]; exists {
		info.Version = version
	} else if version, exists := osInfo["VERSION"]; exists {
		info.Version = version
	}

	if info.Distribution == "" {
		return fmt.Errorf("could not determine distribution from os-release")
	}

	return nil
}

func detectFromLSBRelease(info *Info) error {
	if !utils.CommandExists("lsb_release") {
		return fmt.Errorf("lsb_release command not available")
	}

	distro, err := utils.RunCommand("lsb_release", "-si")
	if err != nil {
		return fmt.Errorf("failed to get distribution ID: %w", err)
	}
	info.Distribution = strings.ToLower(distro)

	version, err := utils.RunCommand("lsb_release", "-sr")
	if err != nil {
		return fmt.Errorf("failed to get distribution version: %w", err)
	}
	info.Version = version

	return nil
}

func detectPackageManager(distro string) string {
	switch strings.ToLower(distro) {
	case "ubuntu", "debian", "linuxmint", "elementary", "pop":
		return "apt"
	case "fedora", "rhel", "centos", "rocky", "almalinux":
		return "dnf"
	case "opensuse", "suse", "opensuse-leap", "opensuse-tumbleweed":
		return "zypper"
	case "arch", "manjaro", "endeavouros", "garuda":
		return "pacman"
	case "gentoo":
		return "portage"
	case "alpine":
		return "apk"
	default:
		if utils.CommandExists("apt") {
			return "apt"
		} else if utils.CommandExists("dnf") {
			return "dnf"
		} else if utils.CommandExists("yum") {
			return "yum"
		} else if utils.CommandExists("zypper") {
			return "zypper"
		} else if utils.CommandExists("pacman") {
			return "pacman"
		} else if utils.CommandExists("apk") {
			return "apk"
		}
		return "unknown"
	}
}

func GatherSystemInfo(ctx context.Context) (*types.SystemInfo, error) {
	sysInfo := &types.SystemInfo{}

	basicInfo, err := DetectSystem(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to detect system: %w", err)
	}

	sysInfo.Distribution = types.DistributionInfo{
		ID:             basicInfo.Distribution,
		Version:        basicInfo.Version,
		PackageManager: basicInfo.PackageManager,
	}

	if cpuInfo, err := gatherCPUInfo(); err == nil {
		sysInfo.CPU = *cpuInfo
	}

	if memInfo, err := gatherMemoryInfo(); err == nil {
		sysInfo.Memory = *memInfo
	}

	if batteryInfo, err := gatherBatteryInfo(); err == nil {
		sysInfo.Battery = batteryInfo
	}

	if powerInfo, err := gatherPowerSupplyInfo(); err == nil {
		sysInfo.PowerSupply = *powerInfo
	}

	if kernelInfo, err := gatherKernelInfo(); err == nil {
		sysInfo.Kernel = *kernelInfo
	}

	if hwInfo, err := gatherHardwareInfo(); err == nil {
		sysInfo.Hardware = *hwInfo
	}

	return sysInfo, nil
}

func gatherCPUInfo() (*types.CPUInfo, error) {
	cpuInfo := &types.CPUInfo{}

	if utils.FileExists("/proc/cpuinfo") {
		lines, err := utils.ReadFileLines("/proc/cpuinfo")
		if err != nil {
			return nil, err
		}

		for _, line := range lines {
			if key, value, ok := utils.ParseKeyValue(line); ok {
				switch strings.ToLower(key) {
				case "model name":
					if cpuInfo.Model == "" {
						cpuInfo.Model = value
					}
				case "vendor_id":
					if cpuInfo.Vendor == "" {
						cpuInfo.Vendor = value
					}
				case "processor":
					cpuInfo.Cores++
				}
			}
		}
	}

	if utils.FileExists("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor") {
		governor, err := utils.ReadFirstLine("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor")
		if err == nil {
			cpuInfo.Governor = governor
		}
	}

	if utils.FileExists("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq") {
		maxFreq, err := utils.ReadFirstLine("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")
		if err == nil {
			cpuInfo.MaxFrequency = utils.ParseInt64(maxFreq) / 1000
		}
	}

	if utils.FileExists("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq") {
		minFreq, err := utils.ReadFirstLine("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq")
		if err == nil {
			cpuInfo.MinFrequency = utils.ParseInt64(minFreq) / 1000
		}
	}

	cpuInfo.Architecture = runtime.GOARCH

	return cpuInfo, nil
}

func gatherMemoryInfo() (*types.MemoryInfo, error) {
	memInfo := &types.MemoryInfo{}

	if !utils.FileExists("/proc/meminfo") {
		return nil, fmt.Errorf("/proc/meminfo not found")
	}

	lines, err := utils.ReadFileLines("/proc/meminfo")
	if err != nil {
		return nil, err
	}

	for _, line := range lines {
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}

		key := strings.TrimSuffix(parts[0], ":")
		value := utils.ParseInt64(parts[1])

		switch key {
		case "MemTotal":
			memInfo.Total = value / 1024
		case "MemAvailable":
			memInfo.Available = value / 1024
		case "SwapTotal":
			memInfo.SwapTotal = value / 1024
		case "SwapFree":
			swapFree := value / 1024
			memInfo.SwapUsed = memInfo.SwapTotal - swapFree
		}
	}

	memInfo.Used = memInfo.Total - memInfo.Available

	return memInfo, nil
}

func gatherBatteryInfo() (*types.BatteryInfo, error) {
	batteryPath := "/sys/class/power_supply/BAT0"
	if !utils.FileExists(batteryPath) {
		batteryPath = "/sys/class/power_supply/BAT1"
		if !utils.FileExists(batteryPath) {
			return nil, fmt.Errorf("no battery found")
		}
	}

	batteryInfo := &types.BatteryInfo{Present: true}

	properties := map[string]*string{
		"status":       &batteryInfo.Status,
		"manufacturer": &batteryInfo.Manufacturer,
		"model_name":   &batteryInfo.Model,
		"technology":   &batteryInfo.Technology,
	}

	for prop, field := range properties {
		if value, err := utils.ReadFirstLine(batteryPath + "/" + prop); err == nil {
			*field = value
		}
	}

	if value, err := utils.ReadFirstLine(batteryPath + "/capacity"); err == nil {
		batteryInfo.Capacity = utils.ParseInt(value)
	}

	if value, err := utils.ReadFirstLine(batteryPath + "/energy_full"); err == nil {
		batteryInfo.EnergyFull = utils.ParseInt64(value) / 1000000
	}

	if value, err := utils.ReadFirstLine(batteryPath + "/energy_now"); err == nil {
		batteryInfo.EnergyNow = utils.ParseInt64(value) / 1000000
	}

	if value, err := utils.ReadFirstLine(batteryPath + "/power_now"); err == nil {
		batteryInfo.PowerNow = utils.ParseInt64(value) / 1000000
	}

	if value, err := utils.ReadFirstLine(batteryPath + "/cycle_count"); err == nil {
		batteryInfo.CycleCount = utils.ParseInt(value)
	}

	if value, err := utils.ReadFirstLine(batteryPath + "/energy_full_design"); err == nil {
		batteryInfo.DesignCapacity = utils.ParseInt64(value) / 1000000
	}

	return batteryInfo, nil
}

func gatherPowerSupplyInfo() (*types.PowerSupplyInfo, error) {
	powerInfo := &types.PowerSupplyInfo{}

	acPaths := []string{
		"/sys/class/power_supply/ADP0",
		"/sys/class/power_supply/ADP1",
		"/sys/class/power_supply/AC",
		"/sys/class/power_supply/ACAD",
	}

	for _, path := range acPaths {
		if utils.FileExists(path + "/online") {
			if online, err := utils.ReadFirstLine(path + "/online"); err == nil {
				powerInfo.ACConnected = online == "1"
				powerInfo.Online = powerInfo.ACConnected
				break
			}
		}
	}

	powerInfo.Type = "AC"

	return powerInfo, nil
}

func gatherKernelInfo() (*types.KernelInfo, error) {
	kernelInfo := &types.KernelInfo{
		Parameters: make(map[string]string),
	}

	if version, err := utils.ReadFirstLine("/proc/version"); err == nil {
		parts := strings.Fields(version)
		if len(parts) >= 3 {
			kernelInfo.Version = parts[2]
		}
	}

	if release, err := utils.ReadFirstLine("/proc/sys/kernel/osrelease"); err == nil {
		kernelInfo.Release = release
	}

	if cmdline, err := utils.ReadFirstLine("/proc/cmdline"); err == nil {
		params := strings.Fields(cmdline)
		for _, param := range params {
			if key, value, ok := utils.ParseKeyValue(param); ok {
				kernelInfo.Parameters[key] = value
			} else {
				kernelInfo.Parameters[param] = ""
			}
		}
	}

	return kernelInfo, nil
}

func gatherHardwareInfo() (*types.HardwareInfo, error) {
	hwInfo := &types.HardwareInfo{}

	if utils.FileExists("/sys/class/dmi/id/chassis_type") {
		if chassis, err := utils.ReadFirstLine("/sys/class/dmi/id/chassis_type"); err == nil {
			hwInfo.Chassis = getChassisType(utils.ParseInt(chassis))
		}
	}

	if utils.FileExists("/sys/class/dmi/id/sys_vendor") {
		if vendor, err := utils.ReadFirstLine("/sys/class/dmi/id/sys_vendor"); err == nil {
			hwInfo.Manufacturer = vendor
		}
	}

	if utils.FileExists("/sys/class/dmi/id/product_name") {
		if product, err := utils.ReadFirstLine("/sys/class/dmi/id/product_name"); err == nil {
			hwInfo.ProductName = product
		}
	}

	hwInfo.StorageDevices = gatherStorageInfo()

	return hwInfo, nil
}

func getChassisType(chassisType int) string {
	types := map[int]string{
		1:  "Other",
		2:  "Unknown",
		3:  "Desktop",
		4:  "Low Profile Desktop",
		5:  "Pizza Box",
		6:  "Mini Tower",
		7:  "Tower",
		8:  "Portable",
		9:  "Laptop",
		10: "Notebook",
		11: "Hand Held",
		12: "Docking Station",
		13: "All In One",
		14: "Sub Notebook",
		15: "Space-saving",
		16: "Lunch Box",
		17: "Main Server Chassis",
		18: "Expansion Chassis",
		19: "Sub Chassis",
		20: "Bus Expansion Chassis",
		21: "Peripheral Chassis",
		22: "RAID Chassis",
		23: "Rack Mount Chassis",
		24: "Sealed-case PC",
		25: "Multi-system",
		26: "CompactPCI",
		27: "AdvancedTCA",
		28: "Blade",
		29: "Blade Enclosing",
	}

	if desc, exists := types[chassisType]; exists {
		return desc
	}
	return "Unknown"
}

func gatherStorageInfo() []types.StorageInfo {
	var devices []types.StorageInfo

	if !utils.FileExists("/proc/partitions") {
		return devices
	}

	lines, err := utils.ReadFileLines("/proc/partitions")
	if err != nil {
		return devices
	}

	for _, line := range lines {
		fields := strings.Fields(line)
		if len(fields) < 4 {
			continue
		}

		deviceName := fields[3]
		if strings.HasPrefix(line, "major") ||
			strings.Contains(deviceName, "loop") ||
			len(deviceName) > 3 && (deviceName[len(deviceName)-1] >= '0' && deviceName[len(deviceName)-1] <= '9') {
			continue
		}

		device := types.StorageInfo{
			Device: "/dev/" + deviceName,
			Size:   utils.ParseInt64(fields[2]) / 1024 / 1024,
		}

		rotationalPath := fmt.Sprintf("/sys/block/%s/queue/rotational", deviceName)
		if utils.FileExists(rotationalPath) {
			if rotational, err := utils.ReadFirstLine(rotationalPath); err == nil {
				device.Rotational = rotational == "1"
				if device.Rotational {
					device.Type = "HDD"
				} else if strings.HasPrefix(deviceName, "nvme") {
					device.Type = "NVMe"
				} else {
					device.Type = "SSD"
				}
			}
		}

		modelPath := fmt.Sprintf("/sys/block/%s/device/model", deviceName)
		if utils.FileExists(modelPath) {
			if model, err := utils.ReadFirstLine(modelPath); err == nil {
				device.Model = strings.TrimSpace(model)
			}
		}

		devices = append(devices, device)
	}

	return devices
}
