package exporter

import (
	"encoding/csv"
	"encoding/json"
	"io"
	"strings"

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

// Exporter handles data export in various formats
type Exporter struct {
	result *models.AnalysisResult
}

// New creates a new exporter
func New(result *models.AnalysisResult) *Exporter {
	return &Exporter{result: result}
}

// ExportCSV exports serial numbers to CSV format
func (e *Exporter) ExportCSV(w io.Writer) error {
	writer := csv.NewWriter(w)
	defer writer.Flush()

	// Header
	if err := writer.Write([]string{"Component", "Serial Number", "Manufacturer", "Location"}); err != nil {
		return err
	}

	if e.result == nil {
		return nil
	}

	// FRU data
	for _, fru := range e.result.FRU {
		if !hasUsableSerial(fru.SerialNumber) {
			continue
		}
		name := fru.ProductName
		if name == "" {
			name = fru.Description
		}
		if err := writer.Write([]string{
			name,
			fru.SerialNumber,
			fru.Manufacturer,
			fru.PartNumber,
		}); err != nil {
			return err
		}
	}

	// Hardware data
	if e.result.Hardware != nil {
		// Board
		if hasUsableSerial(e.result.Hardware.BoardInfo.SerialNumber) {
			if err := writer.Write([]string{
				e.result.Hardware.BoardInfo.ProductName,
				strings.TrimSpace(e.result.Hardware.BoardInfo.SerialNumber),
				e.result.Hardware.BoardInfo.Manufacturer,
				"Board",
			}); err != nil {
				return err
			}
		}

		seenCanonical := make(map[string]struct{})
		for _, dev := range canonicalDevicesForExport(e.result.Hardware) {
			if !hasUsableSerial(dev.SerialNumber) {
				continue
			}
			serial := strings.TrimSpace(dev.SerialNumber)
			seenCanonical[serial] = struct{}{}
			component, manufacturer, location := csvFieldsFromCanonicalDevice(dev)
			if err := writer.Write([]string{component, serial, manufacturer, location}); err != nil {
				return err
			}
		}

		// Legacy network cards
		for _, nic := range e.result.Hardware.NetworkCards {
			if !hasUsableSerial(nic.SerialNumber) {
				continue
			}
			serial := strings.TrimSpace(nic.SerialNumber)
			if _, ok := seenCanonical[serial]; ok {
				continue
			}
			if err := writer.Write([]string{
				nic.Model,
				serial,
				"",
				"Network",
			}); err != nil {
				return err
			}
		}
	}

	return nil
}

// ExportJSON exports all data to JSON format
func (e *Exporter) ExportJSON(w io.Writer) error {
	encoder := json.NewEncoder(w)
	encoder.SetIndent("", "  ")
	return encoder.Encode(e.result)
}

func hasUsableSerial(serial string) bool {
	s := strings.TrimSpace(serial)
	if s == "" {
		return false
	}
	switch strings.ToUpper(s) {
	case "N/A", "NA", "NONE", "NULL", "UNKNOWN", "-":
		return false
	default:
		return true
	}
}

func csvFieldsFromCanonicalDevice(dev models.HardwareDevice) (component, manufacturer, location string) {
	component = firstNonEmptyString(
		dev.Model,
		dev.PartNumber,
		dev.DeviceClass,
		dev.Kind,
	)
	manufacturer = firstNonEmptyString(dev.Manufacturer, inferCSVVendor(dev))
	location = firstNonEmptyString(dev.Location, dev.Slot, dev.BDF, dev.Kind)

	switch dev.Kind {
	case models.DeviceKindCPU:
		if component == "" {
			component = "CPU"
		}
		if location == "" {
			location = "CPU"
		}
	case models.DeviceKindMemory:
		component = firstNonEmptyString(dev.PartNumber, dev.Model, "Memory")
	case models.DeviceKindPCIe, models.DeviceKindGPU, models.DeviceKindNetwork:
		if location == "" {
			location = firstNonEmptyString(dev.Slot, dev.BDF, "PCIe")
		}
	case models.DeviceKindPSU:
		component = firstNonEmptyString(dev.Model, "Power Supply")
	}

	return component, manufacturer, location
}

func inferCSVVendor(dev models.HardwareDevice) string {
	switch dev.Kind {
	case models.DeviceKindCPU:
		return ""
	default:
		return ""
	}
}

func firstNonEmptyString(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}
