package collector

import (
	"bee/audit/internal/schema"
	"log/slog"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

var (
	ethtoolModuleQuery = func(iface string) (string, error) {
		out, err := commandOutputWithTimeout(nicProbeTimeout, "ethtool", "-m", iface)
		if err != nil {
			return "", err
		}
		return string(out), nil
	}
	readNetAddressFile = func(iface string) (string, error) {
		path := filepath.Join("/sys/class/net", iface, "address")
		raw, err := os.ReadFile(path)
		if err != nil {
			return "", err
		}
		return strings.TrimSpace(string(raw)), nil
	}
)

func enrichPCIeWithNICTelemetry(devs []schema.HardwarePCIeDevice) []schema.HardwarePCIeDevice {
	enriched := 0
	for i := range devs {
		if !isNICDevice(devs[i]) || devs[i].BDF == nil {
			continue
		}
		bdf := normalizePCIeBDF(*devs[i].BDF)
		if bdf == "" {
			continue
		}
		ifaces := netIfacesByBDF(bdf)
		if len(ifaces) == 0 {
			continue
		}
		iface := ifaces[0]
		devs[i].MacAddresses = collectInterfaceMACs(ifaces)
		if devs[i].SerialNumber == nil {
			if serial := queryPCIDeviceSerial(bdf); serial != "" {
				devs[i].SerialNumber = &serial
			}
		}

		if devs[i].Firmware == nil {
			if out, err := ethtoolInfoQuery(iface); err == nil {
				if fw := parseEthtoolFirmwareInfo(out); fw != "" {
					devs[i].Firmware = &fw
				}
			}
		}

		if out, err := ethtoolModuleQuery(iface); err == nil {
			if injectSFPDOMTelemetry(&devs[i], out) {
				enriched++
				continue
			}
		}
		if len(devs[i].MacAddresses) > 0 || devs[i].Firmware != nil {
			enriched++
		}
	}
	slog.Info("nic: telemetry enriched", "count", enriched)
	return devs
}

func isNICDevice(dev schema.HardwarePCIeDevice) bool {
	if dev.DeviceClass == nil {
		return false
	}
	c := strings.TrimSpace(*dev.DeviceClass)
	return isNICClass(c) || strings.EqualFold(c, "FibreChannelController")
}

func collectInterfaceMACs(ifaces []string) []string {
	seen := map[string]struct{}{}
	var out []string
	for _, iface := range ifaces {
		mac, err := readNetAddressFile(iface)
		if err != nil || mac == "" {
			continue
		}
		mac = strings.ToLower(strings.TrimSpace(mac))
		if _, ok := seen[mac]; ok {
			continue
		}
		seen[mac] = struct{}{}
		out = append(out, mac)
	}
	return out
}

var floatRe = regexp.MustCompile(`[-+]?[0-9]*\.?[0-9]+`)

func injectSFPDOMTelemetry(dev *schema.HardwarePCIeDevice, raw string) bool {
	var changed bool
	for _, line := range strings.Split(raw, "\n") {
		trimmed := strings.TrimSpace(line)
		if trimmed == "" {
			continue
		}
		idx := strings.Index(trimmed, ":")
		if idx < 0 {
			continue
		}
		key := strings.ToLower(strings.TrimSpace(trimmed[:idx]))
		val := strings.TrimSpace(trimmed[idx+1:])
		if val == "" || strings.EqualFold(val, "not supported") || strings.EqualFold(val, "unknown") {
			continue
		}

		switch {
		case key == "identifier":
			s := parseSFPIdentifier(val)
			dev.SFPIdentifier = &s
			t := true
			dev.SFPPresent = &t
			changed = true
		case key == "connector":
			s := parseSFPConnector(val)
			dev.SFPConnector = &s
			changed = true
		case key == "vendor name":
			s := strings.TrimSpace(val)
			dev.SFPVendor = &s
			changed = true
		case key == "vendor pn":
			s := strings.TrimSpace(val)
			dev.SFPPartNumber = &s
			changed = true
		case key == "vendor sn":
			s := strings.TrimSpace(val)
			dev.SFPSerialNumber = &s
			changed = true
		case strings.Contains(key, "laser wavelength"):
			if f, ok := firstFloat(val); ok {
				dev.SFPWavelengthNM = &f
				changed = true
			}
		case strings.Contains(key, "module temperature"):
			if f, ok := firstFloat(val); ok {
				dev.SFPTemperatureC = &f
				changed = true
			}
		case strings.Contains(key, "laser output power"):
			if f, ok := dbmValue(val); ok {
				dev.SFPTXPowerDBM = &f
				changed = true
			}
		case strings.Contains(key, "receiver signal"):
			if f, ok := dbmValue(val); ok {
				dev.SFPRXPowerDBM = &f
				changed = true
			}
		case strings.Contains(key, "module voltage"):
			if f, ok := firstFloat(val); ok {
				dev.SFPVoltageV = &f
				changed = true
			}
		case strings.Contains(key, "laser bias current"):
			if f, ok := firstFloat(val); ok {
				dev.SFPBiasMA = &f
				changed = true
			}
		}
	}
	return changed
}

// parseSFPIdentifier extracts the human-readable transceiver type from the
// raw ethtool identifier line, e.g. "0x03 (SFP)" → "SFP".
func parseSFPIdentifier(val string) string {
	if s := extractParens(val); s != "" {
		return s
	}
	return val
}

// parseSFPConnector extracts the connector type from the raw ethtool line,
// e.g. "0x07 (LC)" → "LC".
func parseSFPConnector(val string) string {
	if s := extractParens(val); s != "" {
		return s
	}
	return val
}

var parenRe = regexp.MustCompile(`\(([^)]+)\)`)

func extractParens(s string) string {
	m := parenRe.FindStringSubmatch(s)
	if len(m) < 2 {
		return ""
	}
	return strings.TrimSpace(m[1])
}

func parseSFPDOM(raw string) map[string]any {
	dev := schema.HardwarePCIeDevice{}
	if !injectSFPDOMTelemetry(&dev, raw) {
		return map[string]any{}
	}
	out := map[string]any{}
	if dev.SFPPresent != nil {
		out["sfp_present"] = *dev.SFPPresent
	}
	if dev.SFPIdentifier != nil {
		out["sfp_identifier"] = *dev.SFPIdentifier
	}
	if dev.SFPConnector != nil {
		out["sfp_connector"] = *dev.SFPConnector
	}
	if dev.SFPVendor != nil {
		out["sfp_vendor"] = *dev.SFPVendor
	}
	if dev.SFPPartNumber != nil {
		out["sfp_part_number"] = *dev.SFPPartNumber
	}
	if dev.SFPSerialNumber != nil {
		out["sfp_serial_number"] = *dev.SFPSerialNumber
	}
	if dev.SFPWavelengthNM != nil {
		out["sfp_wavelength_nm"] = *dev.SFPWavelengthNM
	}
	if dev.SFPTemperatureC != nil {
		out["sfp_temperature_c"] = *dev.SFPTemperatureC
	}
	if dev.SFPTXPowerDBM != nil {
		out["sfp_tx_power_dbm"] = *dev.SFPTXPowerDBM
	}
	if dev.SFPRXPowerDBM != nil {
		out["sfp_rx_power_dbm"] = *dev.SFPRXPowerDBM
	}
	if dev.SFPVoltageV != nil {
		out["sfp_voltage_v"] = *dev.SFPVoltageV
	}
	if dev.SFPBiasMA != nil {
		out["sfp_bias_ma"] = *dev.SFPBiasMA
	}
	return out
}

func firstFloat(raw string) (float64, bool) {
	m := floatRe.FindString(raw)
	if m == "" {
		return 0, false
	}
	v, err := strconv.ParseFloat(m, 64)
	if err != nil {
		return 0, false
	}
	return v, true
}

func dbmValue(raw string) (float64, bool) {
	parts := strings.Split(strings.ToLower(raw), "dbm")
	if len(parts) == 0 {
		return 0, false
	}
	for i := len(parts) - 1; i >= 0; i-- {
		candidate := parts[i]
		matches := floatRe.FindAllString(candidate, -1)
		if len(matches) == 0 {
			continue
		}
		v, err := strconv.ParseFloat(matches[len(matches)-1], 64)
		if err == nil {
			return v, true
		}
	}
	return 0, false
}
