package app

import (
	"fmt"
	"os/exec"
	"strings"
)

type ThunderboltInfo struct {
	Items []ThunderboltBus `json:"SPThunderboltDataType"`
}

type StorageItem struct {
	Name          string `json:"_name"`
	MountPoint    string `json:"mount_point"`
	PhysicalDrive struct {
		DeviceName string `json:"device_name"`
		IsInternal string `json:"is_internal_disk"`
		Protocol   string `json:"protocol"`
		MediumType string `json:"medium_type"`
	} `json:"physical_drive"`
}

type ThunderboltBus struct {
	Name          string                 `json:"_name"`
	Vendor        string                 `json:"vendor_name_key"`
	DomainUUID    string                 `json:"domain_uuid_key"`
	SwitchUID     string                 `json:"switch_uid_key"`
	Receptacle    *ThunderboltReceptacle `json:"receptacle_1_tag"`
	ConnectedDevs []ThunderboltDevice    `json:"_items"`
	NetworkStats  *ThunderboltNetStats   `json:"network_stats,omitempty"`
}

type ThunderboltReceptacle struct {
	Status       string `json:"receptacle_status_key"`
	CurrentSpeed string `json:"current_speed_key"`
	ReceptacleID string `json:"receptacle_id_key"`
}

type ThunderboltDevice struct {
	Name       string `json:"_name"`
	Vendor     string `json:"vendor_name_key"`
	VendorID   string `json:"vendor_id_key"`
	Mode       string `json:"mode_key"`
	DeviceName string `json:"device_name_key"`
	SwitchUID  string `json:"switch_uid_key"`
	DeviceID   string `json:"device_id_key"`
	DomainUUID string `json:"domain_uuid_key"`
}

// USB device types for SPUSBDataType
type USBInfo struct {
	Items []USBBus `json:"SPUSBDataType"`
}

type USBBus struct {
	Name       string      `json:"_name"`
	HostCtrl   string      `json:"host_controller"`
	PCIDev     string      `json:"pci_device"`
	PCIVendor  string      `json:"pci_vendor"`
	USBDevices []USBDevice `json:"_items"`
}

type USBDevice struct {
	Name         string `json:"_name"`
	Manufacturer string `json:"manufacturer"`
	ProductID    string `json:"product_id"`
	VendorID     string `json:"vendor_id"`
	Speed        string `json:"device_speed"`
	LocationID   string `json:"location_id"`
}

// USBDeviceWithPort represents a USB device with its USB-C port number
type USBDeviceWithPort struct {
	Device     ThunderboltDeviceOutput
	PortNumber string // USB-C port number (maps to TB receptacle_id)
}

func GetUSBDevicesFromItems(items []StorageItem) []USBDeviceWithPort {
	// First, get USB device port mappings from ioreg
	portMap := getUSBDevicePortMap()

	var devices []USBDeviceWithPort
	seen := make(map[string]bool)

	for _, vol := range items {
		if vol.PhysicalDrive.IsInternal != "no" {
			continue
		}
		protocol := vol.PhysicalDrive.Protocol
		if protocol != "USB" && protocol != "Thunderbolt" {
			continue
		}

		devName := vol.PhysicalDrive.DeviceName
		if devName == "" {
			devName = vol.Name
		}
		if seen[devName] {
			continue
		}
		seen[devName] = true

		devInfo := protocol
		if vol.PhysicalDrive.MediumType != "" {
			devInfo += ", " + strings.ToUpper(vol.PhysicalDrive.MediumType)
		}

		// Look for port number in port map (match by device name substring)
		portNum := ""
		for key, port := range portMap {
			if strings.Contains(strings.ToLower(devName), strings.ToLower(key)) ||
				strings.Contains(strings.ToLower(key), strings.ToLower(devName)) {
				portNum = port
				break
			}
		}

		devices = append(devices, USBDeviceWithPort{
			Device: ThunderboltDeviceOutput{
				Name:   devName,
				Vendor: "",
				Mode:   protocol,
				Info:   devInfo,
			},
			PortNumber: portNum,
		})
	}
	return devices
}

// getUSBDevicePortMap returns a map of USB device names to their USB-C port numbers
func getUSBDevicePortMap() map[string]string {
	return parseIOUSBHostDeviceGrep()
}

func parseIOUSBHostDeviceGrep() map[string]string {
	portMap := make(map[string]string)

	// Use grep-like extraction
	cmd2 := exec.Command("bash", "-c", `ioreg -r -c IOUSBHostDevice 2>/dev/null | grep -E "USB Product Name|UsbCPortNumber" | paste - - 2>/dev/null`)
	out2, _ := cmd2.Output()
	for line := range strings.Lines(string(out2)) {
		// Parse paired lines like: "USB Product Name" = "ASM236X series" ... "UsbCPortNumber" = 5
		if strings.Contains(line, "USB Product Name") && strings.Contains(line, "UsbCPortNumber") {
			// Extract product name
			nameStart := strings.Index(line, `"USB Product Name" = "`)
			if nameStart >= 0 {
				nameStart += len(`"USB Product Name" = "`)
				nameEnd := strings.Index(line[nameStart:], `"`)
				if nameEnd > 0 {
					prodName := line[nameStart : nameStart+nameEnd]
					// Extract port number
					portStart := strings.Index(line, `"UsbCPortNumber" = `)
					if portStart >= 0 {
						portStart += len(`"UsbCPortNumber" = `)
						portEnd := portStart
						for portEnd < len(line) && line[portEnd] >= '0' && line[portEnd] <= '9' {
							portEnd++
						}
						if portEnd > portStart {
							portMap[prodName] = line[portStart:portEnd]
						}
					}
				}
			}
		}
	}
	return portMap
}

type ThunderboltOutput struct {
	Buses []ThunderboltBusOutput `json:"buses"`
}

type ThunderboltBusOutput struct {
	Name         string                    `json:"name"`
	Status       string                    `json:"status"` // Active, Inactive
	Icon         string                    `json:"icon"`   // ⚡, ○
	Speed        string                    `json:"speed,omitempty"`
	DomainUUID   string                    `json:"domain_uuid,omitempty"`
	SwitchUID    string                    `json:"switch_uid,omitempty"`
	ReceptacleID string                    `json:"receptacle_id,omitempty"`
	Devices      []ThunderboltDeviceOutput `json:"devices,omitempty"`
	NetworkStats *ThunderboltNetStats      `json:"network_stats,omitempty"`
	RDMADevice   *RDMADevice               `json:"rdma_device,omitempty"`
}

type ThunderboltDeviceOutput struct {
	Name       string `json:"name"`
	Vendor     string `json:"vendor,omitempty"`
	VendorID   string `json:"vendor_id,omitempty"`
	Mode       string `json:"mode,omitempty"`
	SwitchUID  string `json:"switch_uid,omitempty"`
	DeviceID   string `json:"device_id,omitempty"`
	DomainUUID string `json:"domain_uuid,omitempty"`
	Info       string `json:"info_string,omitempty"`
}

func GetFormattedThunderboltInfo() (*ThunderboltOutput, error) {
	// Use combined fetch to get all data in one process execution
	combinedData, err := GetGlobalProfilerData()
	if err != nil {
		return nil, err
	}

	tbInfo := &ThunderboltInfo{Items: combinedData.ThunderboltItems}
	usbDevices := GetUSBDevicesFromItems(combinedData.StorageItems)

	maxPortCapability := getMaxPortCapability(tbInfo.Items)
	output := &ThunderboltOutput{}
	for _, bus := range tbInfo.Items {
		output.Buses = append(output.Buses, processThunderboltBus(bus, maxPortCapability))
	}

	// assignUSBDevicesToBuses calls GetUSBDevicesWithPorts internaly. We should refactor it to accept the list.
	assignUSBDevicesToBuses(output, tbInfo.Items, usbDevices)

	return output, nil
}

func getMaxPortCapability(items []ThunderboltBus) string {
	maxPortCapability := "TB4" // Default for modern Macs
	for _, bus := range items {
		if bus.Receptacle != nil {
			speed := bus.Receptacle.CurrentSpeed
			if strings.Contains(speed, "120") || strings.Contains(speed, "80 Gb") {
				maxPortCapability = "TB5"
				break
			}
		}
	}
	return maxPortCapability
}

func getBusNumber(busName string) string {
	if strings.Contains(busName, "_bus_") {
		parts := strings.Split(busName, "_bus_")
		if len(parts) > 1 {
			return parts[1]
		}
	}
	if strings.Contains(busName, " Bus ") {
		parts := strings.Split(busName, " Bus ")
		if len(parts) > 1 {
			return parts[1]
		}
	}
	return ""
}

func getBusActivityInfo(bus ThunderboltBus) (bool, string, string) {
	isActive := false
	speed := ""
	activeProtocol := ""

	if bus.Receptacle != nil {
		if bus.Receptacle.Status == "receptacle_connected" {
			isActive = true
		}
		if bus.Receptacle.CurrentSpeed != "" {
			speed = bus.Receptacle.CurrentSpeed
			if isActive && !strings.Contains(speed, "Up to") {
				if strings.Contains(speed, "120") || strings.Contains(speed, "80") {
					activeProtocol = "TB5"
				} else if strings.Contains(speed, "40") {
					activeProtocol = "TB4"
				} else if strings.Contains(speed, "20") {
					activeProtocol = "TB3"
				}
			}
		}
	} else if len(bus.ConnectedDevs) > 0 {
		isActive = true
	}
	return isActive, speed, activeProtocol
}

func formatConnectedDevices(devices []ThunderboltDevice) []ThunderboltDeviceOutput {
	var outputs []ThunderboltDeviceOutput
	for _, dev := range devices {
		devName := dev.Name
		if devName == "" {
			devName = dev.DeviceName
		}

		devInfo := ""
		if dev.Vendor != "" {
			devInfo = fmt.Sprintf("%s", dev.Vendor)
		}
		modePretty := getFormattedMode(dev.Mode)

		if modePretty != "" {
			if devInfo != "" {
				devInfo += ", " + modePretty
			} else {
				devInfo = modePretty
			}
		}

		outputs = append(outputs, ThunderboltDeviceOutput{
			Name:       devName,
			Vendor:     dev.Vendor,
			VendorID:   dev.VendorID,
			Mode:       modePretty,
			SwitchUID:  dev.SwitchUID,
			DeviceID:   dev.DeviceID,
			DomainUUID: dev.DomainUUID,
			Info:       devInfo,
		})
	}
	return outputs
}

func getFormattedMode(rawMode string) string {
	if rawMode == "" {
		return ""
	}
	mode := strings.ToLower(rawMode)
	mode = strings.ReplaceAll(mode, "_", " ")
	switch {
	case strings.Contains(mode, "thunderbolt 5") || strings.Contains(mode, "thunderbolt5") || strings.Contains(mode, "thunderbolt five"):
		return "TB5"
	case strings.Contains(mode, "thunderbolt 4") || strings.Contains(mode, "thunderbolt4") || strings.Contains(mode, "thunderbolt four"):
		return "TB4"
	case strings.Contains(mode, "thunderbolt 3") || strings.Contains(mode, "thunderbolt3") || strings.Contains(mode, "thunderbolt three"):
		return "TB3"
	case strings.Contains(mode, "usb4") || strings.Contains(mode, "usb 4"):
		return "USB4"
	default:
		return strings.Title(strings.ReplaceAll(rawMode, "_", " "))
	}
}

func processThunderboltBus(bus ThunderboltBus, maxPortCapability string) ThunderboltBusOutput {
	busNum := getBusNumber(bus.Name)
	isActive, speed, activeProtocol := getBusActivityInfo(bus)

	var busLabel string
	if isActive && activeProtocol != "" && activeProtocol != maxPortCapability {
		busLabel = fmt.Sprintf("%s @ %s Bus %s", maxPortCapability, activeProtocol, busNum)
	} else {
		busLabel = fmt.Sprintf("%s Bus %s", maxPortCapability, busNum)
	}

	statusStr := "Inactive"
	icon := "○"
	if isActive {
		statusStr = "Active"
		icon = "ϟ"
	}

	receptacleID := ""
	if bus.Receptacle != nil {
		receptacleID = bus.Receptacle.ReceptacleID
	}

	busOut := ThunderboltBusOutput{
		Name:         busLabel,
		Status:       statusStr,
		Icon:         icon,
		Speed:        speed,
		DomainUUID:   bus.DomainUUID,
		SwitchUID:    bus.SwitchUID,
		ReceptacleID: receptacleID,
		Devices:      formatConnectedDevices(bus.ConnectedDevs),
	}
	return busOut
}

func assignUSBDevicesToBuses(output *ThunderboltOutput, infoItems []ThunderboltBus, usbDevicesWithPorts []USBDeviceWithPort) {
	for _, usbDev := range usbDevicesWithPorts {
		matched := false
		if usbDev.PortNumber != "" {
			for i := range output.Buses {
				for _, bus := range infoItems {
					busNum := getBusNumber(bus.Name)
					if strings.HasSuffix(output.Buses[i].Name, "Bus "+busNum) && bus.Receptacle != nil {
						if bus.Receptacle.ReceptacleID == usbDev.PortNumber {
							output.Buses[i].Devices = append(output.Buses[i].Devices, usbDev.Device)
							if output.Buses[i].Status == "Inactive" {
								output.Buses[i].Status = "Active (USB)"
								output.Buses[i].Icon = "⏺"
							}
							matched = true
							break
						}
					}
				}
				if matched {
					break
				}
			}
		}
		if !matched {
			for i := range output.Buses {
				if output.Buses[i].Status == "Inactive" && len(output.Buses[i].Devices) == 0 {
					output.Buses[i].Devices = append(output.Buses[i].Devices, usbDev.Device)
					output.Buses[i].Status = "Active (USB)"
					output.Buses[i].Icon = "⏺"
					break
				}
			}
		}
	}
}

func GetThunderboltDescription() string {
	formatted, err := GetFormattedThunderboltInfo()
	if err != nil {
		return "Error loading Thunderbolt info."
	}
	if len(formatted.Buses) == 0 {
		return "No Thunderbolt controllers found."
	}

	var sb strings.Builder
	for _, bus := range formatted.Buses {
		speedStr := ""
		if bus.Speed != "" {
			speedStr = " @ " + bus.Speed
		}
		fmt.Fprintf(&sb, "%s %s (%s)%s\n", bus.Icon, bus.Name, bus.Status, speedStr)

		if len(bus.Devices) > 0 {
			for i, dev := range bus.Devices {
				prefix := "  ├─"
				if i == len(bus.Devices)-1 {
					prefix = "  └─"
				}
				if dev.Info != "" {
					fmt.Fprintf(&sb, "%s %s (%s)\n", prefix, dev.Name, dev.Info)
				} else {
					fmt.Fprintf(&sb, "%s %s\n", prefix, dev.Name)
				}
			}
		}
	}

	// Apple Silicon Macs connected via Thunderbolt do not register as
	// IOThunderboltSwitch children on the local side — they only surface as
	// Thunderbolt Bridge (bridge0) member interfaces. List those so a
	// peer-host TB5 connection (e.g. used for RDMA) is visible alongside the
	// devices enumerated by IOKit.
	bridgePeers := listThunderboltBridgePeers()
	if len(bridgePeers) > 0 {
		sb.WriteString("Thunderbolt Bridge:\n")
		for i, iface := range bridgePeers {
			prefix := "  ├─"
			if i == len(bridgePeers)-1 {
				prefix = "  └─"
			}
			fmt.Fprintf(&sb, "%s %s (peer host)\n", prefix, iface)
		}
	}

	return strings.TrimSpace(sb.String())
}

// listThunderboltBridgePeers returns the bridge member interface names
// (en2, en3, ...) that indicate a Thunderbolt Bridge link to a peer host.
// Excludes "bridge0" itself.
func listThunderboltBridgePeers() []string {
	members := getTBBridgeMembers()
	var ifaces []string
	for name := range members {
		if name == "bridge0" {
			continue
		}
		if strings.HasPrefix(name, "en") {
			ifaces = append(ifaces, name)
		}
	}
	return ifaces
}
