package pciids

import (
	"bufio"
	_ "embed"
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync"
)

var (
	//go:embed pci.ids
	embeddedPCIIDs string

	loadOnce sync.Once
	vendors  map[int]string
	devices  map[string]string
)

// VendorName returns vendor name by PCI Vendor ID
func VendorName(vendorID int) string {
	loadPCIIDs()
	if name, ok := vendors[vendorID]; ok {
		return name
	}
	return ""
}

// DeviceName returns device name by Vendor ID and Device ID
func DeviceName(vendorID, deviceID int) string {
	loadPCIIDs()
	key := fmt.Sprintf("%04x:%04x", vendorID, deviceID)
	if name, ok := devices[key]; ok {
		return name
	}
	return ""
}

// DeviceInfo returns both vendor and device name
func DeviceInfo(vendorID, deviceID int) (vendor, device string) {
	vendor = VendorName(vendorID)
	device = DeviceName(vendorID, deviceID)
	return
}

// VendorNameFromString tries to parse vendor ID from string (hex) and return name
func VendorNameFromString(s string) string {
	s = strings.TrimSpace(s)
	if s == "" {
		return ""
	}

	// Try to parse as hex (with or without 0x prefix)
	s = strings.TrimPrefix(strings.ToLower(s), "0x")

	var id int
	for _, c := range s {
		if c >= '0' && c <= '9' {
			id = id*16 + int(c-'0')
		} else if c >= 'a' && c <= 'f' {
			id = id*16 + int(c-'a'+10)
		} else {
			return ""
		}
	}

	return VendorName(id)
}

func loadPCIIDs() {
	loadOnce.Do(func() {
		vendors = make(map[int]string)
		devices = make(map[string]string)

		parsePCIIDs(strings.NewReader(embeddedPCIIDs), vendors, devices)

		for _, path := range candidatePCIIDsPaths() {
			f, err := os.Open(path)
			if err != nil {
				continue
			}
			parsePCIIDs(f, vendors, devices)
			_ = f.Close()
		}
	})
}

func candidatePCIIDsPaths() []string {
	paths := []string{
		"pci.ids",
		"/usr/share/hwdata/pci.ids",
		"/usr/share/misc/pci.ids",
		"/opt/homebrew/share/pciids/pci.ids",
	}

	// Env paths have highest priority, so they are applied last.
	if env := strings.TrimSpace(os.Getenv("LOGPILE_PCI_IDS_PATH")); env != "" {
		for _, p := range strings.Split(env, string(os.PathListSeparator)) {
			p = strings.TrimSpace(p)
			if p != "" {
				paths = append(paths, p)
			}
		}
	}
	return paths
}

func parsePCIIDs(r interface{ Read([]byte) (int, error) }, outVendors map[int]string, outDevices map[string]string) {
	scanner := bufio.NewScanner(r)
	currentVendor := -1

	for scanner.Scan() {
		line := scanner.Text()
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		// Subdevice line (tab-tab) - ignored for now
		if strings.HasPrefix(line, "\t\t") {
			continue
		}

		// Device line
		if strings.HasPrefix(line, "\t") {
			if currentVendor < 0 {
				continue
			}
			trimmed := strings.TrimLeft(line, "\t")
			fields := strings.Fields(trimmed)
			if len(fields) < 2 {
				continue
			}
			deviceID, err := strconv.ParseInt(fields[0], 16, 32)
			if err != nil {
				continue
			}
			name := strings.TrimSpace(trimmed[len(fields[0]):])
			if name == "" {
				continue
			}
			key := fmt.Sprintf("%04x:%04x", currentVendor, int(deviceID))
			outDevices[key] = name
			continue
		}

		// Vendor line
		fields := strings.Fields(line)
		if len(fields) < 2 {
			currentVendor = -1
			continue
		}
		vendorID, err := strconv.ParseInt(fields[0], 16, 32)
		if err != nil {
			currentVendor = -1
			continue
		}
		name := strings.TrimSpace(line[len(fields[0]):])
		if name == "" {
			currentVendor = -1
			continue
		}
		currentVendor = int(vendorID)
		outVendors[currentVendor] = name
	}
}
