package nvidia

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

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

// UnifiedSummaryData represents the structure of unified_summary.json
type UnifiedSummaryData struct {
	RunInfo RunInfo `json:"runInfo"`
	Tests   []Test  `json:"tests"`
}

// RunInfo contains information about the diagnostic run
type RunInfo struct {
	TimeInfo struct {
		StartTime     string `json:"startTime"`
		EndTime       string `json:"endTime"`
		TotalDuration string `json:"totalDuration"`
	} `json:"timeInfo"`
	DiagVersion string `json:"diagVersion"`
	BaseVersion string `json:"baseVersion"`
	FinalResult string `json:"finalResult"`
	ErrorCode   int    `json:"errorCode"`
	DiagName    string `json:"diagName"`
	RunLevel    string `json:"runLevel"`
}

// Test represents a diagnostic test
type Test struct {
	VirtualID  string      `json:"virtualId"`
	Action     string      `json:"action"`
	StartTime  string      `json:"startTime"`
	EndTime    string      `json:"endTime"`
	Components []Component `json:"components"`
}

// Component represents a hardware component
type Component struct {
	ComponentID string     `json:"componentId"`
	ErrorCode   string     `json:"errorCode"`
	Notes       string     `json:"notes"`
	Result      string     `json:"result"`
	Properties  []Property `json:"properties"`
}

// Property represents a component property
type Property struct {
	ID    string      `json:"id"`
	Value interface{} `json:"value"` // Can be string or number
}

var nvswitchComponentIDRegex = regexp.MustCompile(`^(NVSWITCH\d+|NVSWITCHNVSWITCH\d+)$`)

// GetValueAsString returns the value as a string
func (p *Property) GetValueAsString() string {
	switch v := p.Value.(type) {
	case string:
		return v
	case float64:
		return fmt.Sprintf("%.0f", v)
	case int:
		return fmt.Sprintf("%d", v)
	default:
		return fmt.Sprintf("%v", v)
	}
}

// ParseUnifiedSummary parses unified_summary.json file
func ParseUnifiedSummary(content []byte, result *models.AnalysisResult) error {
	var data UnifiedSummaryData
	if err := json.Unmarshal(content, &data); err != nil {
		return fmt.Errorf("failed to parse unified_summary.json: %w", err)
	}

	// Set default board info only if not already set (from output.log)
	if result.Hardware.BoardInfo.ProductName == "" {
		result.Hardware.BoardInfo.ProductName = "GPU Server (Field Diag)"
	}

	// Parse inventory test for hardware details
	for _, test := range data.Tests {
		if test.VirtualID == "inventory" || test.Action == "inventory" {
			parseInventoryComponents(test.Components, result)
		}
	}

	return nil
}

// parseInventoryComponents extracts hardware info from inventory test
func parseInventoryComponents(components []Component, result *models.AnalysisResult) {
	for _, comp := range components {
		// Parse system/board information
		if parseSystemInfo(comp, result) {
			// System info was found and parsed
			continue
		}

		// Parse GPU components
		if strings.HasPrefix(comp.ComponentID, "GPUSXM") {
			gpu := parseGPUComponent(comp)
			if gpu != nil {
				result.Hardware.GPUs = append(result.Hardware.GPUs, *gpu)
			}
		}

		// Parse NVSwitch components
		if isNVSwitchComponentID(comp.ComponentID) {
			nvswitch := parseNVSwitchComponent(comp)
			if nvswitch != nil {
				// Add as PCIe device for now
				result.Hardware.PCIeDevices = append(result.Hardware.PCIeDevices, *nvswitch)
			}
		}
	}
}

// parseSystemInfo extracts system/board information from a component
// Returns true if this component contains system info
func parseSystemInfo(comp Component, result *models.AnalysisResult) bool {
	compID := strings.ToUpper(comp.ComponentID)

	// Check if this is a system/board component
	isSystemComponent := strings.Contains(compID, "BASEBOARD") ||
		strings.Contains(compID, "SYSTEM") ||
		strings.Contains(compID, "MOTHERBOARD") ||
		strings.Contains(compID, "BOARD") ||
		comp.ComponentID == "Inventory"

	if !isSystemComponent {
		return false
	}

	// Extract system properties
	for _, prop := range comp.Properties {
		propID := prop.ID
		value := prop.GetValueAsString()

		if value == "" {
			continue
		}

		switch propID {
		case "Manufacturer", "BoardManufacturer", "SystemManufacturer":
			// Only set if not already populated (e.g., from output.log)
			if result.Hardware.BoardInfo.Manufacturer == "" {
				result.Hardware.BoardInfo.Manufacturer = value
			}
		case "ProductName", "Product", "Model", "ModelName", "BoardProduct", "SystemProduct":
			// Don't overwrite real data from output.log with generic data
			// Only set if empty or still has the default placeholder value
			if result.Hardware.BoardInfo.ProductName == "" ||
				result.Hardware.BoardInfo.ProductName == "GPU Server (Field Diag)" {
				result.Hardware.BoardInfo.ProductName = value
			}
		case "SerialNumber", "Serial", "BoardSerial", "SystemSerial":
			// Only set if not already populated (e.g., from output.log)
			if result.Hardware.BoardInfo.SerialNumber == "" {
				result.Hardware.BoardInfo.SerialNumber = value
			}
		case "PartNumber", "BoardPartNumber":
			// Only set if not already populated
			if result.Hardware.BoardInfo.PartNumber == "" {
				result.Hardware.BoardInfo.PartNumber = value
			}
		}
	}

	return true
}

// parseGPUComponent parses GPU component information
func parseGPUComponent(comp Component) *models.GPU {
	gpu := &models.GPU{
		Slot: comp.ComponentID, // e.g., "GPUSXM1"
	}

	var deviceID, vbios, pciID string

	for _, prop := range comp.Properties {
		switch prop.ID {
		case "DeviceID":
			deviceID = prop.GetValueAsString()
			if deviceID != "" {
				fmt.Sscanf(deviceID, "%x", &gpu.DeviceID)
			}
		case "Vendor":
			gpu.Manufacturer = prop.GetValueAsString()
		case "DeviceName":
			gpu.Model = prop.GetValueAsString()
		case "VBIOS_version":
			vbios = prop.GetValueAsString()
		case "PCIID":
			pciID = prop.GetValueAsString()
		}
	}

	// Build model string from vendor/device IDs
	if gpu.Model == "" || strings.Contains(gpu.Model, "Device") {
		if deviceID != "" {
			gpu.Model = fmt.Sprintf("NVIDIA Device %s", strings.ToUpper(deviceID))
		}
	}

	// Add firmware info
	if vbios != "" {
		gpu.Firmware = vbios
	}

	// Add PCI info
	if pciID != "" {
		gpu.BDF = pciID
	}

	return gpu
}

// parseNVSwitchComponent parses NVSwitch component information
func parseNVSwitchComponent(comp Component) *models.PCIeDevice {
	device := &models.PCIeDevice{
		Slot: normalizeNVSwitchSlot(comp.ComponentID),
	}

	var vendorIDStr, deviceIDStr, vbios, pciID string
	var pciSpeedStr, pciWidthStr string
	var vendor string

	for _, prop := range comp.Properties {
		switch prop.ID {
		case "VendorID":
			vendorIDStr = prop.GetValueAsString()
		case "DeviceID":
			deviceIDStr = prop.GetValueAsString()
		case "Vendor":
			vendor = prop.GetValueAsString()
		case "VBIOS_version":
			vbios = prop.GetValueAsString()
		case "InfoROM_version":
			// Store in part number field as we don't have a better place
		case "PCIID":
			pciID = prop.GetValueAsString()
			device.BDF = pciID
		case "PCISpeed":
			pciSpeedStr = prop.GetValueAsString()
			device.LinkSpeed = pciSpeedStr
			device.MaxLinkSpeed = pciSpeedStr
		case "PCIWidth":
			pciWidthStr = prop.GetValueAsString()
		}
	}

	// Parse vendor ID
	if vendorIDStr != "" {
		fmt.Sscanf(vendorIDStr, "%x", &device.VendorID)
	}

	// Parse device ID
	if deviceIDStr != "" {
		fmt.Sscanf(deviceIDStr, "%x", &device.DeviceID)
	}

	// Set manufacturer
	if vendor != "" {
		device.Manufacturer = vendor
	}

	// Set device class
	device.DeviceClass = "NVSwitch"

	// Parse link width
	if pciWidthStr != "" {
		fmt.Sscanf(pciWidthStr, "x%d", &device.LinkWidth)
		device.MaxLinkWidth = device.LinkWidth
	}

	// Store part number (use for firmware version)
	if vbios != "" {
		device.PartNumber = vbios
	}

	return device
}

func normalizeNVSwitchSlot(componentID string) string {
	slot := strings.TrimSpace(componentID)
	if strings.HasPrefix(slot, "NVSWITCHNVSWITCH") {
		return strings.Replace(slot, "NVSWITCHNVSWITCH", "NVSWITCH", 1)
	}
	return slot
}

func isNVSwitchComponentID(componentID string) bool {
	return nvswitchComponentIDRegex.MatchString(strings.TrimSpace(componentID))
}
