// Package unraid provides parser for Unraid diagnostics archives.
package unraid

import (
	"bufio"
	"regexp"
	"strconv"
	"strings"
	"time"

	"git.mchus.pro/mchus/logpile/internal/models"
	"git.mchus.pro/mchus/logpile/internal/parser"
	"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
)

// parserVersion - increment when parsing logic changes.
const parserVersion = "1.2"

func init() {
	parser.Register(&Parser{})
}

// Parser implements VendorParser for Unraid diagnostics.
type Parser struct{}

func (p *Parser) Name() string    { return "Unraid Parser" }
func (p *Parser) Vendor() string  { return "unraid" }
func (p *Parser) Version() string { return parserVersion }

// Detect checks if files contain typical Unraid markers.
func (p *Parser) Detect(files []parser.ExtractedFile) int {
	confidence := 0
	hasUnraidVersion := false
	hasDiagnosticsDir := false
	hasVarsParity := false

	for _, f := range files {
		path := strings.ToLower(f.Path)
		content := string(f.Content)

		// Check for unraid version file
		if strings.Contains(path, "unraid-") && strings.HasSuffix(path, ".txt") {
			hasUnraidVersion = true
			confidence += 40
		}

		// Check for Unraid-specific directories
		if strings.Contains(path, "diagnostics-") &&
			(strings.Contains(path, "/system/") ||
				strings.Contains(path, "/smart/") ||
				strings.Contains(path, "/config/")) {
			hasDiagnosticsDir = true
			if confidence < 60 {
				confidence += 20
			}
		}

		// Check file content for Unraid markers
		if strings.Contains(content, "Unraid kernel build") {
			confidence += 50
		}

		// Check for vars.txt with disk array info
		if strings.Contains(path, "vars.txt") && strings.Contains(content, "[parity]") {
			hasVarsParity = true
			confidence += 30
		}

		if confidence >= 100 {
			return 100
		}
	}

	// Boost confidence if we see multiple key indicators together
	if hasUnraidVersion && (hasDiagnosticsDir || hasVarsParity) {
		confidence += 20
	}

	if confidence > 100 {
		return 100
	}
	return confidence
}

// Parse parses Unraid diagnostics and returns normalized data.
func (p *Parser) Parse(files []parser.ExtractedFile) (*models.AnalysisResult, error) {
	result := &models.AnalysisResult{
		Events:  make([]models.Event, 0),
		FRU:     make([]models.FRUInfo, 0),
		Sensors: make([]models.SensorReading, 0),
		Hardware: &models.HardwareConfig{
			Firmware: make([]models.FirmwareInfo, 0),
			CPUs:     make([]models.CPU, 0),
			Memory:   make([]models.MemoryDIMM, 0),
			Storage:  make([]models.Storage, 0),
		},
	}

	// Track storage by slot to avoid duplicates
	storageBySlot := make(map[string]*models.Storage)
	hasDetailedMemory := false
	ethtoolByIface := make(map[string]ethtoolInfo)
	ethtoolByBDF := make(map[string]ethtoolInfo)
	ifconfigByIface := make(map[string]ifconfigInfo)

	// Parse different file types
	for _, f := range files {
		path := strings.ToLower(f.Path)
		content := string(f.Content)

		switch {
		case strings.Contains(path, "unraid-") && strings.HasSuffix(path, ".txt"):
			parseVersionFile(content, result)

		case strings.HasSuffix(path, "/system/lscpu.txt") || strings.HasSuffix(path, "\\system\\lscpu.txt"):
			parseLsCPU(content, result)

		case strings.HasSuffix(path, "/system/motherboard.txt") || strings.HasSuffix(path, "\\system\\motherboard.txt"):
			parseMotherboard(content, result)

		case strings.HasSuffix(path, "/system/memory.txt") || strings.HasSuffix(path, "\\system\\memory.txt"):
			parseMemory(content, result)

		case strings.HasSuffix(path, "/system/meminfo.txt") || strings.HasSuffix(path, "\\system\\meminfo.txt"):
			if parseMemoryDIMMs(content, result) > 0 {
				hasDetailedMemory = true
			}

		case strings.HasSuffix(path, "/system/ifconfig.txt") || strings.HasSuffix(path, "\\system\\ifconfig.txt"):
			parseIfconfig(content, ifconfigByIface)

		case strings.HasSuffix(path, "/system/ethtool.txt") || strings.HasSuffix(path, "\\system\\ethtool.txt"):
			parseEthtool(content, ethtoolByIface, ethtoolByBDF)

		case strings.HasSuffix(path, "/system/lspci.txt") || strings.HasSuffix(path, "\\system\\lspci.txt"):
			parseLSPCI(content, ifconfigByIface, ethtoolByIface, ethtoolByBDF, result)

		case strings.HasSuffix(path, "/system/vars.txt") || strings.HasSuffix(path, "\\system\\vars.txt"):
			parseVarsToMap(content, storageBySlot, result)
			parseHostIdentityFromVars(content, result)

		case strings.Contains(path, "/smart/") && strings.HasSuffix(path, ".txt"):
			parseSMARTFileToMap(content, f.Path, storageBySlot, result)

		case strings.HasSuffix(path, "/logs/syslog.txt") || strings.HasSuffix(path, "\\logs\\syslog.txt"):
			parseSyslog(content, result)
		}
	}

	if hasDetailedMemory {
		filtered := make([]models.MemoryDIMM, 0, len(result.Hardware.Memory))
		for _, dimm := range result.Hardware.Memory {
			if strings.EqualFold(strings.TrimSpace(dimm.Slot), "system") {
				continue
			}
			filtered = append(filtered, dimm)
		}
		result.Hardware.Memory = filtered
	}

	// Convert storage map to slice
	for _, disk := range storageBySlot {
		result.Hardware.Storage = append(result.Hardware.Storage, *disk)
	}

	return result, nil
}

func parseVersionFile(content string, result *models.AnalysisResult) {
	lines := strings.Split(content, "\n")
	if len(lines) > 0 {
		version := strings.TrimSpace(lines[0])
		if version != "" {
			result.Hardware.Firmware = append(result.Hardware.Firmware, models.FirmwareInfo{
				DeviceName: "Unraid OS",
				Version:    version,
			})
		}
	}
}

func parseLsCPU(content string, result *models.AnalysisResult) {
	// Normalize line endings
	content = strings.ReplaceAll(content, "\r\n", "\n")

	var cpu models.CPU
	cpu.Socket = 0 // Default to socket 0

	// Parse CPU model - handle multiple spaces
	if m := regexp.MustCompile(`(?m)^Model name:\s+(.+)$`).FindStringSubmatch(content); len(m) == 2 {
		cpu.Model = strings.TrimSpace(m[1])
	}

	// Parse CPU(s) - total thread count
	if m := regexp.MustCompile(`(?m)^CPU\(s\):\s+(\d+)$`).FindStringSubmatch(content); len(m) == 2 {
		cpu.Threads = parseInt(m[1])
	}

	// Parse cores per socket
	if m := regexp.MustCompile(`(?m)^Core\(s\) per socket:\s+(\d+)$`).FindStringSubmatch(content); len(m) == 2 {
		cpu.Cores = parseInt(m[1])
	}

	// Parse CPU max MHz
	if m := regexp.MustCompile(`(?m)^CPU max MHz:\s+([\d.]+)$`).FindStringSubmatch(content); len(m) == 2 {
		cpu.FrequencyMHz = int(parseFloat(m[1]))
	}

	// If no max MHz, try current MHz
	if cpu.FrequencyMHz == 0 {
		if m := regexp.MustCompile(`(?m)^CPU MHz:\s+([\d.]+)$`).FindStringSubmatch(content); len(m) == 2 {
			cpu.FrequencyMHz = int(parseFloat(m[1]))
		}
	}

	// Only add if we got at least the model
	if cpu.Model != "" {
		result.Hardware.CPUs = append(result.Hardware.CPUs, cpu)
	}
}

func parseMotherboard(content string, result *models.AnalysisResult) {
	var board models.BoardInfo

	// Parse manufacturer from dmidecode output
	lines := strings.Split(content, "\n")
	inBIOSSection := false

	for _, line := range lines {
		trimmed := strings.TrimSpace(line)

		if strings.Contains(trimmed, "BIOS Information") {
			inBIOSSection = true
			continue
		}

		if inBIOSSection {
			if strings.HasPrefix(trimmed, "Vendor:") {
				parts := strings.SplitN(trimmed, ":", 2)
				if len(parts) == 2 {
					board.Manufacturer = strings.TrimSpace(parts[1])
				}
			} else if strings.HasPrefix(trimmed, "Version:") {
				parts := strings.SplitN(trimmed, ":", 2)
				if len(parts) == 2 {
					biosVersion := strings.TrimSpace(parts[1])
					result.Hardware.Firmware = append(result.Hardware.Firmware, models.FirmwareInfo{
						DeviceName: "System BIOS",
						Version:    biosVersion,
					})
				}
			} else if strings.HasPrefix(trimmed, "Release Date:") {
				// Could extract BIOS date if needed
			}
		}
	}

	// Extract product name from first line
	if len(lines) > 0 {
		firstLine := strings.TrimSpace(lines[0])
		if firstLine != "" {
			board.ProductName = firstLine
		}
	}

	result.Hardware.BoardInfo = board
}

func parseMemory(content string, result *models.AnalysisResult) {
	// Parse memory from free output
	// Example: Mem:            50Gi        11Gi       1.4Gi       565Mi        39Gi        39Gi
	if m := regexp.MustCompile(`(?m)^Mem:\s+(\d+(?:\.\d+)?)(Ki|Mi|Gi|Ti|KB|MB|GB|TB)`).FindStringSubmatch(content); len(m) >= 3 {
		size := parseFloat(m[1])
		unit := strings.ToUpper(m[2])

		var sizeMB int
		switch unit {
		case "KI", "KB":
			sizeMB = int(size / 1024)
		case "MI", "MB":
			sizeMB = int(size)
		case "GI", "GB":
			sizeMB = int(size * 1024)
		case "TI", "TB":
			sizeMB = int(size * 1024 * 1024)
		}

		if sizeMB > 0 {
			result.Hardware.Memory = append(result.Hardware.Memory, models.MemoryDIMM{
				Slot:    "system",
				Present: true,
				SizeMB:  sizeMB,
				Type:    "DRAM",
				Status:  "ok",
			})
		}
	}
}

func parseMemoryDIMMs(content string, result *models.AnalysisResult) int {
	blocks := strings.Split(content, "Handle ")
	added := 0
	for _, block := range blocks {
		b := strings.TrimSpace(block)
		if b == "" || !strings.Contains(b, "DMI type 17") || !strings.Contains(b, "Memory Device") {
			continue
		}

		sizeRaw := extractFieldValue(b, "Size:")
		if sizeRaw == "" || strings.Contains(strings.ToLower(sizeRaw), "no module installed") {
			continue
		}

		sizeMB := parseDIMMSizeMB(sizeRaw)
		if sizeMB <= 0 {
			continue
		}

		slot := extractFieldValue(b, "Locator:")
		if slot == "" {
			slot = extractFieldValue(b, "Bank Locator:")
		}
		if slot == "" {
			slot = "dimm"
		}

		dimm := models.MemoryDIMM{
			Slot:            slot,
			Location:        extractFieldValue(b, "Bank Locator:"),
			Present:         true,
			SizeMB:          sizeMB,
			Type:            extractFieldValue(b, "Type:"),
			MaxSpeedMHz:     parseSpeedMTs(extractFieldValue(b, "Speed:")),
			CurrentSpeedMHz: parseSpeedMTs(extractFieldValue(b, "Configured Memory Speed:")),
			Manufacturer:    strings.TrimSpace(extractFieldValue(b, "Manufacturer:")),
			SerialNumber:    strings.TrimSpace(extractFieldValue(b, "Serial Number:")),
			PartNumber:      strings.TrimSpace(extractFieldValue(b, "Part Number:")),
			Ranks:           parseInt(extractFieldValue(b, "Rank:")),
			Status:          "ok",
		}
		if dimm.Type == "" || strings.EqualFold(dimm.Type, "Unknown") {
			dimm.Type = "DRAM"
		}
		if dimm.CurrentSpeedMHz == 0 {
			dimm.CurrentSpeedMHz = dimm.MaxSpeedMHz
		}

		result.Hardware.Memory = append(result.Hardware.Memory, dimm)
		added++
	}
	return added
}

func extractFieldValue(block, key string) string {
	for _, line := range strings.Split(block, "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, key) {
			return strings.TrimSpace(strings.TrimPrefix(line, key))
		}
	}
	return ""
}

func parseDIMMSizeMB(s string) int {
	s = strings.TrimSpace(strings.ToUpper(s))
	if s == "" {
		return 0
	}
	parts := strings.Fields(s)
	if len(parts) < 2 {
		return 0
	}
	v := parseFloat(parts[0])
	switch parts[1] {
	case "KB", "KIB":
		return int(v / 1024)
	case "MB", "MIB":
		return int(v)
	case "GB", "GIB":
		return int(v * 1024)
	case "TB", "TIB":
		return int(v * 1024 * 1024)
	default:
		return 0
	}
}

func parseSpeedMTs(s string) int {
	s = strings.TrimSpace(strings.ToUpper(s))
	if s == "" {
		return 0
	}
	re := regexp.MustCompile(`(\d+)\s*MT/S`)
	if m := re.FindStringSubmatch(s); len(m) == 2 {
		return parseInt(m[1])
	}
	return 0
}

type ethtoolInfo struct {
	Interface string
	BusInfo   string
	Driver    string
	Firmware  string
	SpeedMbps int
	LinkUp    bool
}

type ifconfigInfo struct {
	Interface string
	State     string
	Addresses []string
}

func parseIfconfig(content string, out map[string]ifconfigInfo) {
	lines := strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		fields := strings.Fields(line)
		if len(fields) < 2 {
			continue
		}
		iface := strings.Split(fields[0], "@")[0]
		if iface == "" || strings.HasPrefix(iface, "lo") || strings.HasPrefix(iface, "docker") || strings.HasPrefix(iface, "veth") {
			continue
		}
		state := fields[1]
		addrs := make([]string, 0, 2)
		for _, f := range fields[2:] {
			if strings.Contains(f, ".") || strings.Contains(f, ":") {
				addrs = append(addrs, f)
			}
		}
		out[iface] = ifconfigInfo{
			Interface: iface,
			State:     state,
			Addresses: addrs,
		}
	}
}

func parseEthtool(content string, byIface, byBDF map[string]ethtoolInfo) {
	sections := strings.Split(content, "--------------------------------")
	for _, sec := range sections {
		s := strings.TrimSpace(sec)
		if s == "" {
			continue
		}
		var info ethtoolInfo
		for _, line := range strings.Split(s, "\n") {
			t := strings.TrimSpace(line)
			switch {
			case strings.HasPrefix(t, "Settings for "):
				info.Interface = strings.TrimSuffix(strings.TrimPrefix(t, "Settings for "), ":")
			case strings.HasPrefix(t, "driver:"):
				info.Driver = strings.TrimSpace(strings.TrimPrefix(t, "driver:"))
			case strings.HasPrefix(t, "firmware-version:"):
				info.Firmware = strings.TrimSpace(strings.TrimPrefix(t, "firmware-version:"))
			case strings.HasPrefix(t, "bus-info:"):
				info.BusInfo = normalizeBDF(strings.TrimSpace(strings.TrimPrefix(t, "bus-info:")))
			case strings.HasPrefix(t, "Speed:"):
				info.SpeedMbps = parseSpeedMbps(strings.TrimSpace(strings.TrimPrefix(t, "Speed:")))
			case strings.HasPrefix(t, "Link detected:"):
				info.LinkUp = strings.EqualFold(strings.TrimSpace(strings.TrimPrefix(t, "Link detected:")), "yes")
			}
		}
		if info.Interface != "" {
			byIface[info.Interface] = info
		}
		if info.BusInfo != "" {
			byBDF[info.BusInfo] = info
		}
	}
}

func parseLSPCI(
	content string,
	iface map[string]ifconfigInfo,
	ethtoolByIface map[string]ethtoolInfo,
	ethtoolByBDF map[string]ethtoolInfo,
	result *models.AnalysisResult,
) {
	lines := strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n")
	lspciLineRe := regexp.MustCompile(`^([0-9a-fA-F:.]+)\s+(.+?)\s+\[[0-9a-fA-F]{4}\]:\s+(.+?)\s+\[([0-9a-fA-F]{4}):([0-9a-fA-F]{4})\]`)

	hasPCIe := make(map[string]bool)
	hasAdapter := make(map[string]bool)

	for _, line := range lines {
		m := lspciLineRe.FindStringSubmatch(strings.TrimSpace(line))
		if len(m) != 6 {
			continue
		}

		bdf := normalizeBDF(m[1])
		class := strings.TrimSpace(m[2])
		desc := strings.TrimSpace(m[3])
		vendorID := parseHexID(m[4])
		deviceID := parseHexID(m[5])

		if bdf == "" {
			continue
		}

		if isInterestingPCIClass(class) && !hasPCIe[bdf] {
			vendor := pciids.VendorName(vendorID)
			result.Hardware.PCIeDevices = append(result.Hardware.PCIeDevices, models.PCIeDevice{
				Slot:         bdf,
				BDF:          bdf,
				DeviceClass:  class,
				Description:  desc,
				VendorID:     vendorID,
				DeviceID:     deviceID,
				Manufacturer: vendor,
				Status:       "ok",
			})
			hasPCIe[bdf] = true
		}

		if !isNICClass(class) || hasAdapter[bdf] {
			continue
		}

		etInfo := ethtoolByBDF[bdf]
		if etInfo.Interface == "" {
			for _, it := range ethtoolByIface {
				if normalizeBDF(it.BusInfo) == bdf {
					etInfo = it
					break
				}
			}
		}
		if etInfo.Driver == "bonding" {
			continue
		}

		model := desc
		if devName := pciids.DeviceName(vendorID, deviceID); devName != "" {
			model = devName
		}
		vendor := pciids.VendorName(vendorID)
		if vendor == "" {
			vendor = firstWords(desc, 2)
		}

		slot := etInfo.Interface
		if slot == "" {
			slot = bdf
		}
		status := "ok"
		if etInfo.Interface != "" && !etInfo.LinkUp {
			status = "warning"
		} else if etInfo.Interface != "" {
			if ifInfo, ok := iface[etInfo.Interface]; ok && !strings.EqualFold(ifInfo.State, "UP") {
				status = "warning"
			}
		}

		adapter := models.NetworkAdapter{
			Slot:      slot,
			Location:  bdf,
			Present:   true,
			Model:     model,
			Vendor:    vendor,
			VendorID:  vendorID,
			DeviceID:  deviceID,
			Firmware:  strings.TrimSpace(etInfo.Firmware),
			PortCount: 1,
			Status:    status,
		}
		result.Hardware.NetworkAdapters = append(result.Hardware.NetworkAdapters, adapter)

		result.Hardware.NetworkCards = append(result.Hardware.NetworkCards, models.NIC{
			Name:      slot,
			Model:     model,
			SpeedMbps: etInfo.SpeedMbps,
		})

		hasAdapter[bdf] = true
	}
}

func isNICClass(class string) bool {
	c := strings.ToLower(strings.TrimSpace(class))
	return strings.Contains(c, "ethernet controller") || strings.Contains(c, "network controller")
}

func isInterestingPCIClass(class string) bool {
	c := strings.ToLower(strings.TrimSpace(class))
	if isNICClass(c) {
		return true
	}
	switch {
	case strings.Contains(c, "scsi storage controller"),
		strings.Contains(c, "sata controller"),
		strings.Contains(c, "raid bus controller"),
		strings.Contains(c, "vga compatible controller"),
		strings.Contains(c, "3d controller"),
		strings.Contains(c, "display controller"),
		strings.Contains(c, "non-volatile memory controller"),
		strings.Contains(c, "processing accelerators"):
		return true
	default:
		return false
	}
}

func parseHexID(s string) int {
	v, err := strconv.ParseInt(strings.TrimSpace(s), 16, 32)
	if err != nil {
		return 0
	}
	return int(v)
}

func parseSpeedMbps(s string) int {
	s = strings.TrimSpace(strings.ToUpper(s))
	if s == "" || s == "UNKNOWN!" {
		return 0
	}
	if m := regexp.MustCompile(`(\d+)MB/S`).FindStringSubmatch(s); len(m) == 2 {
		return parseInt(m[1])
	}
	return 0
}

func normalizeBDF(bdf string) string {
	bdf = strings.TrimSpace(strings.ToLower(bdf))
	if bdf == "" {
		return ""
	}
	if strings.Count(bdf, ":") == 1 {
		return "0000:" + bdf
	}
	return bdf
}

func firstWords(s string, n int) string {
	parts := strings.Fields(strings.TrimSpace(s))
	if len(parts) == 0 {
		return ""
	}
	if len(parts) < n {
		n = len(parts)
	}
	return strings.Join(parts[:n], " ")
}

func parseVarsToMap(content string, storageBySlot map[string]*models.Storage, result *models.AnalysisResult) {
	// Normalize line endings
	content = strings.ReplaceAll(content, "\r\n", "\n")

	// Parse PHP-style array from vars.txt
	// Extract only the first "disks" section to avoid duplicates
	disksStart := strings.Index(content, "disks\n(")
	if disksStart == -1 {
		return
	}

	// Find the end of this disks array (look for next top-level key or end)
	remaining := content[disksStart:]
	endPattern := regexp.MustCompile(`(?m)^[a-z_]+\n\(`)
	endMatches := endPattern.FindAllStringIndex(remaining, -1)

	var disksSection string
	if len(endMatches) > 1 {
		// Use second match as end (first match is "disks" itself)
		disksSection = remaining[:endMatches[1][0]]
	} else {
		disksSection = remaining
	}

	// Look for disk entries within this section only
	diskRe := regexp.MustCompile(`(?m)^\s+\[(disk\d+|parity|cache\d*)\]\s+=>\s+Array`)
	matches := diskRe.FindAllStringSubmatch(disksSection, -1)

	seen := make(map[string]bool)
	for _, match := range matches {
		if len(match) < 2 {
			continue
		}
		diskName := match[1]

		// Skip if already processed
		if seen[diskName] {
			continue
		}
		seen[diskName] = true

		// Find the section for this disk
		diskSection := extractDiskSection(disksSection, diskName)
		if diskSection == "" {
			continue
		}

		var disk models.Storage
		disk.Slot = diskName

		// Parse disk properties
		if m := regexp.MustCompile(`\[device\]\s*=>\s*(\w+)`).FindStringSubmatch(diskSection); len(m) == 2 {
			disk.Interface = "SATA (" + m[1] + ")"
		}

		if m := regexp.MustCompile(`\[id\]\s*=>\s*([^\n]+)`).FindStringSubmatch(diskSection); len(m) == 2 {
			idValue := strings.TrimSpace(m[1])
			// Only use if it's not empty or a placeholder
			if idValue != "" && !strings.Contains(idValue, "=>") {
				disk.Model = idValue
			}
		}

		if m := regexp.MustCompile(`\[size\]\s*=>\s*(\d+)`).FindStringSubmatch(diskSection); len(m) == 2 {
			sizeKB := parseInt(m[1])
			if sizeKB > 0 {
				disk.SizeGB = sizeKB / (1024 * 1024) // Convert KB to GB
			}
		}

		if m := regexp.MustCompile(`\[temp\]\s*=>\s*(\d+)`).FindStringSubmatch(diskSection); len(m) == 2 {
			temp := parseInt(m[1])
			if temp > 0 {
				result.Sensors = append(result.Sensors, models.SensorReading{
					Name:     diskName + "_temp",
					Type:     "temperature",
					Value:    float64(temp),
					Unit:     "C",
					Status:   getTempStatus(temp),
					RawValue: strconv.Itoa(temp),
				})
			}
		}

		if m := regexp.MustCompile(`\[fsType\]\s*=>\s*(\w+)`).FindStringSubmatch(diskSection); len(m) == 2 {
			fsType := m[1]
			if fsType != "" && fsType != "auto" {
				disk.Type = fsType
			}
		}

		disk.Present = true

		// Only add/merge disks with meaningful data
		if disk.Model != "" && disk.SizeGB > 0 {
			// Check if we already have this disk from SMART files
			if existing, ok := storageBySlot[diskName]; ok {
				// Merge vars.txt data into existing entry, preferring SMART data
				if existing.Model == "" && disk.Model != "" {
					existing.Model = disk.Model
				}
				if existing.SizeGB == 0 && disk.SizeGB > 0 {
					existing.SizeGB = disk.SizeGB
				}
				if existing.Type == "" && disk.Type != "" {
					existing.Type = disk.Type
				}
				if existing.Interface == "" && disk.Interface != "" {
					existing.Interface = disk.Interface
				}
				// vars.txt doesn't have serial/firmware, so don't overwrite from SMART
			} else {
				// New disk not in SMART data
				storageBySlot[diskName] = &disk
			}
		}
	}
}

func parseHostIdentityFromVars(content string, result *models.AnalysisResult) {
	if result == nil || result.Hardware == nil {
		return
	}
	serial := strings.TrimSpace(result.Hardware.BoardInfo.SerialNumber)
	if isUsableHostIdentifier(serial) {
		return
	}

	flashGUID := findVarValue(content, "flashGUID")
	regGUID := findVarValue(content, "regGUID")
	rawUUID := findVarValue(content, "uuid")

	candidates := []string{flashGUID, regGUID, rawUUID}
	for _, candidate := range candidates {
		candidate = strings.TrimSpace(candidate)
		if !isUsableHostIdentifier(candidate) {
			continue
		}
		result.Hardware.BoardInfo.SerialNumber = candidate
		if result.Hardware.BoardInfo.UUID == "" && candidate == rawUUID {
			result.Hardware.BoardInfo.UUID = candidate
		}
		return
	}
}

func findVarValue(content, key string) string {
	re := regexp.MustCompile(`(?m)^\s*\[` + regexp.QuoteMeta(key) + `\]\s*=>\s*(.+?)\s*$`)
	if m := re.FindStringSubmatch(content); len(m) == 2 {
		return strings.TrimSpace(m[1])
	}
	return ""
}

func isUsableHostIdentifier(v string) bool {
	v = strings.TrimSpace(v)
	if v == "" {
		return false
	}
	l := strings.ToLower(v)
	if l == "n/a" || l == "unknown" || l == "none" || l == "not available" {
		return false
	}
	// Unraid may redact GUID values as 1... or 1..7 in diagnostics.
	if strings.Contains(v, "...") || strings.Contains(v, "..") {
		return false
	}
	return true
}

func extractDiskSection(content, diskName string) string {
	// Find the start of this disk's array section
	startPattern := regexp.MustCompile(`(?m)^\s+\[` + regexp.QuoteMeta(diskName) + `\]\s+=>\s+Array\s*\n\s+\(`)
	startIdx := startPattern.FindStringIndex(content)
	if startIdx == nil {
		return ""
	}

	// Find the end (next disk or end of disks array)
	endPattern := regexp.MustCompile(`(?m)^\s+\)`)
	remainingContent := content[startIdx[1]:]
	endIdx := endPattern.FindStringIndex(remainingContent)

	if endIdx == nil {
		return remainingContent
	}

	return remainingContent[:endIdx[0]]
}

func parseSMARTFileToMap(content, filePath string, storageBySlot map[string]*models.Storage, result *models.AnalysisResult) {
	// Extract disk name from filename
	// Example: ST4000NM000B-2TF100_WX103EC9-20260205-2333 disk1 (sdi).txt
	diskName := ""
	if m := regexp.MustCompile(`(disk\d+|parity|cache\d*)`).FindStringSubmatch(filePath); len(m) > 0 {
		diskName = m[1]
	}
	if diskName == "" {
		return
	}

	var disk models.Storage
	disk.Slot = diskName

	// Parse device model
	if m := regexp.MustCompile(`(?m)^Device Model:\s+(.+)$`).FindStringSubmatch(content); len(m) == 2 {
		disk.Model = strings.TrimSpace(m[1])
	}

	// Parse serial number
	if m := regexp.MustCompile(`(?m)^Serial Number:\s+(.+)$`).FindStringSubmatch(content); len(m) == 2 {
		disk.SerialNumber = strings.TrimSpace(m[1])
	}

	// Parse firmware version
	if m := regexp.MustCompile(`(?m)^Firmware Version:\s+(.+)$`).FindStringSubmatch(content); len(m) == 2 {
		disk.Firmware = strings.TrimSpace(m[1])
	}

	// Parse capacity
	if m := regexp.MustCompile(`(?m)^User Capacity:\s+([\d,]+)\s+bytes`).FindStringSubmatch(content); len(m) == 2 {
		capacityStr := strings.ReplaceAll(m[1], ",", "")
		if capacity, err := strconv.ParseInt(capacityStr, 10, 64); err == nil {
			disk.SizeGB = int(capacity / 1_000_000_000)
		}
	}

	// Parse rotation rate
	if m := regexp.MustCompile(`(?m)^Rotation Rate:\s+(.+)$`).FindStringSubmatch(content); len(m) == 2 {
		rateStr := strings.TrimSpace(m[1])
		if strings.Contains(strings.ToLower(rateStr), "solid state") {
			disk.Type = "ssd"
		} else {
			disk.Type = "hdd"
		}
	}

	// Parse SATA version for interface
	if m := regexp.MustCompile(`(?m)^SATA Version is:\s+(.+?)(?:,|$)`).FindStringSubmatch(content); len(m) == 2 {
		disk.Interface = strings.TrimSpace(m[1])
	}

	// Parse SMART health
	if m := regexp.MustCompile(`(?m)^SMART overall-health self-assessment test result:\s+(.+)$`).FindStringSubmatch(content); len(m) == 2 {
		health := strings.TrimSpace(m[1])
		if !strings.EqualFold(health, "PASSED") {
			result.Events = append(result.Events, models.Event{
				Timestamp:   time.Now(),
				Source:      "SMART",
				EventType:   "Disk Health",
				Severity:    models.SeverityWarning,
				Description: "SMART health check failed for " + diskName,
				RawData:     health,
			})
		}
	}

	disk.Present = true

	// Only add/merge if we got meaningful data
	if disk.Model != "" || disk.SerialNumber != "" {
		// Check if we already have this disk from vars.txt
		if existing, ok := storageBySlot[diskName]; ok {
			// Merge SMART data into existing entry
			if existing.Model == "" && disk.Model != "" {
				existing.Model = disk.Model
			}
			if existing.SerialNumber == "" && disk.SerialNumber != "" {
				existing.SerialNumber = disk.SerialNumber
			}
			if existing.Firmware == "" && disk.Firmware != "" {
				existing.Firmware = disk.Firmware
			}
			if existing.SizeGB == 0 && disk.SizeGB > 0 {
				existing.SizeGB = disk.SizeGB
			}
			if existing.Type == "" && disk.Type != "" {
				existing.Type = disk.Type
			}
			if existing.Interface == "" && disk.Interface != "" {
				existing.Interface = disk.Interface
			}
		} else {
			// New disk not in vars.txt
			storageBySlot[diskName] = &disk
		}
	}
}

func parseSyslog(content string, result *models.AnalysisResult) {
	scanner := bufio.NewScanner(strings.NewReader(content))
	scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
	lineCount := 0
	maxLines := 100 // Limit parsing to avoid too many events

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

		// Parse syslog line
		// Example: Feb  5 23:33:01 box3 kernel: Linux version 6.12.54-Unraid
		timestamp, message, severity := parseSyslogLine(line)

		result.Events = append(result.Events, models.Event{
			Timestamp:   timestamp,
			Source:      "syslog",
			EventType:   "System Log",
			Severity:    severity,
			Description: message,
			RawData:     line,
		})

		lineCount++
	}

	if err := scanner.Err(); err != nil {
		result.Events = append(result.Events, models.Event{
			Timestamp:   time.Now(),
			Source:      "syslog",
			EventType:   "System Log",
			Severity:    models.SeverityWarning,
			Description: "syslog scan error",
			RawData:     err.Error(),
		})
	}
}

func parseSyslogLine(line string) (time.Time, string, models.Severity) {
	// Simple syslog parser
	// Format: Feb  5 23:33:01 hostname process[pid]: message
	timestamp := time.Now()
	message := line
	severity := models.SeverityInfo

	// Try to parse timestamp
	syslogRe := regexp.MustCompile(`^(\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})\s+\S+\s+(.+)$`)
	if m := syslogRe.FindStringSubmatch(line); len(m) == 3 {
		timeStr := m[1]
		message = m[2]

		// Parse timestamp (add current year)
		year := time.Now().Year()
		if ts, err := parser.ParseInDefaultArchiveLocation("Jan 2 15:04:05 2006", timeStr+" "+strconv.Itoa(year)); err == nil {
			timestamp = ts
		}
	}

	// Classify severity
	lowerMsg := strings.ToLower(message)
	switch {
	case strings.Contains(lowerMsg, "panic"),
		strings.Contains(lowerMsg, "fatal"),
		strings.Contains(lowerMsg, "critical"):
		severity = models.SeverityCritical

	case strings.Contains(lowerMsg, "error"),
		strings.Contains(lowerMsg, "warning"),
		strings.Contains(lowerMsg, "failed"):
		severity = models.SeverityWarning

	default:
		severity = models.SeverityInfo
	}

	return timestamp, message, severity
}

func getTempStatus(temp int) string {
	switch {
	case temp >= 60:
		return "critical"
	case temp >= 50:
		return "warning"
	default:
		return "ok"
	}
}

func parseInt(s string) int {
	v, _ := strconv.Atoi(strings.TrimSpace(s))
	return v
}

func parseFloat(s string) float64 {
	v, _ := strconv.ParseFloat(strings.TrimSpace(s), 64)
	return v
}
