package nvidia

import (
	"bufio"
	"regexp"
	"strings"

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

var (
	// Regex to extract devname mappings from fieldiag command line
	// Example: "devname=0000:ba:00.0,SXM5_SN_1653925027099"
	devnameRegex = regexp.MustCompile(`devname=([\da-fA-F:\.]+),(\w+)`)
	// Regex to capture BDF from commands like:
	// "$ lspci -vvvs 0000:05:00.0" or "$ lspci -vvs 0000:05:00.0"
	lspciBDFRegex = regexp.MustCompile(`^\$\s+lspci\s+-[^\s]*\s+([0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7])\s*$`)
	// Example: "Capabilities: [2f0 v1] Device Serial Number 99-d3-61-c8-ac-2d-b0-48"
	deviceSerialRegex = regexp.MustCompile(`Device Serial Number\s+([0-9a-fA-F\-:]+)`)
)

// ParseInventoryLog parses inventory/output.log to extract GPU serial numbers
// from fieldiag devname parameters (e.g., "SXM5_SN_1653925027099")
func ParseInventoryLog(content []byte, result *models.AnalysisResult) error {
	if result.Hardware == nil || len(result.Hardware.GPUs) == 0 {
		// No GPUs to update
		return nil
	}

	scanner := bufio.NewScanner(strings.NewReader(string(content)))

	// First pass: build mapping of PCI BDF -> Slot name and serial number from fieldiag command line
	pciToSlot := make(map[string]string)
	pciToSerial := make(map[string]string)
	for scanner.Scan() {
		line := scanner.Text()
		// Look for fieldiag command with devname parameters
		if strings.Contains(line, "devname=") && strings.Contains(line, "fieldiag") {
			matches := devnameRegex.FindAllStringSubmatch(line, -1)
			for _, match := range matches {
				if len(match) == 3 {
					pciBDF := match[1]
					slotName := match[2]
					// Extract slot number and serial from name like "SXM5_SN_1653925027099"
					if strings.HasPrefix(slotName, "SXM") {
						parts := strings.Split(slotName, "_")
						if len(parts) >= 1 {
							// Convert "SXM5" to "GPUSXM5"
							slot := "GPU" + parts[0]
							pciToSlot[pciBDF] = slot
						}
						// Extract serial number from "SXM5_SN_1653925027099"
						if len(parts) == 3 && parts[1] == "SN" {
							serial := parts[2]
							pciToSerial[pciBDF] = serial
						}
					}
				}
			}
		}
	}

	// Second pass: assign serial numbers to GPUs based on slot mapping
	for i := range result.Hardware.GPUs {
		slot := result.Hardware.GPUs[i].Slot
		// Find the PCI BDF for this slot
		var foundSerial string
		for pciBDF, mappedSlot := range pciToSlot {
			if mappedSlot == slot {
				// Found matching slot, get serial number
				if serial, ok := pciToSerial[pciBDF]; ok {
					foundSerial = serial
					break
				}
			}
		}
		if foundSerial != "" {
			result.Hardware.GPUs[i].SerialNumber = foundSerial
		}
	}

	// Third pass: parse lspci "Device Serial Number" by BDF (useful for NVSwitch serials).
	bdfToDeviceSerial := make(map[string]string)
	currentBDF := ""
	scanner = bufio.NewScanner(strings.NewReader(string(content)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		if m := lspciBDFRegex.FindStringSubmatch(line); len(m) == 2 {
			currentBDF = strings.ToLower(strings.TrimSpace(m[1]))
			continue
		}

		if currentBDF == "" {
			continue
		}

		if m := deviceSerialRegex.FindStringSubmatch(line); len(m) == 2 {
			serial := strings.TrimSpace(m[1])
			if serial != "" {
				bdfToDeviceSerial[currentBDF] = serial
			}
			currentBDF = ""
		}
	}

	// Apply to PCIe devices first (includes NVSwitch).
	for i := range result.Hardware.PCIeDevices {
		dev := &result.Hardware.PCIeDevices[i]
		if strings.TrimSpace(dev.SerialNumber) != "" {
			continue
		}
		bdf := strings.ToLower(strings.TrimSpace(dev.BDF))
		if bdf == "" {
			continue
		}
		if serial := bdfToDeviceSerial[bdf]; serial != "" {
			dev.SerialNumber = serial
		}
	}

	// Apply to GPUs only if GPU serial is still empty (do not overwrite prod serial from devname).
	for i := range result.Hardware.GPUs {
		gpu := &result.Hardware.GPUs[i]
		if strings.TrimSpace(gpu.SerialNumber) != "" {
			continue
		}
		bdf := strings.ToLower(strings.TrimSpace(gpu.BDF))
		if bdf == "" {
			continue
		}
		if serial := bdfToDeviceSerial[bdf]; serial != "" {
			gpu.SerialNumber = serial
		}
	}

	return scanner.Err()
}

// findInventoryOutputLog finds the inventory/output.log file
func findInventoryOutputLog(files []parser.ExtractedFile) *parser.ExtractedFile {
	for _, f := range files {
		// Look for inventory/output.log
		path := strings.ToLower(f.Path)
		if strings.Contains(path, "inventory/output.log") ||
			strings.Contains(path, "inventory\\output.log") {
			return &f
		}
	}
	return nil
}
