//go:build windows

package client

import (
	"context"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"

	"github.com/duffy/usb-server/internal/protocol"
	"github.com/duffy/usb-server/internal/usbip"
)

// createVHCIAttachment creates a VHCI attachment on Windows using usbip-win2.
// It starts a local TCP proxy, launches usbip.exe to connect to it,
// handles the USB/IP management phase (OP_REQ_IMPORT) locally,
// and returns the TCP connection for the transfer phase bridge.
func createVHCIAttachment(ctx context.Context, granted *protocol.DeviceGranted, devInfo *RemoteDevice) (net.Conn, int, error) {
	// Find usbip.exe
	usbipExe, err := usbip.FindUsbipExe()
	if err != nil {
		return nil, -1, err
	}

	// Start TCP listener on localhost with random port
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return nil, -1, fmt.Errorf("starting TCP listener: %w", err)
	}
	tcpPort := listener.Addr().(*net.TCPAddr).Port
	log.Printf("[vhci-win] TCP proxy listening on 127.0.0.1:%d", tcpPort)

	// Channel for the accepted connection after management phase
	type acceptResult struct {
		conn net.Conn
		err  error
	}
	resultCh := make(chan acceptResult, 1)

	// Accept connection and handle management phase in goroutine
	go func() {
		conn, err := listener.Accept()
		listener.Close() // only accept one connection
		if err != nil {
			resultCh <- acceptResult{nil, fmt.Errorf("accepting connection: %w", err)}
			return
		}

		log.Printf("[vhci-win] usbip.exe connected, handling management phase")

		// Handle OP_REQ_IMPORT from usbip.exe
		if err := handleImportRequest(conn, granted, devInfo); err != nil {
			conn.Close()
			resultCh <- acceptResult{nil, fmt.Errorf("management phase: %w", err)}
			return
		}

		log.Printf("[vhci-win] management phase complete, entering transfer phase")
		resultCh <- acceptResult{conn, nil}
	}()

	// Launch usbip.exe attach
	cmd := exec.CommandContext(ctx, usbipExe,
		"--tcp-port", fmt.Sprintf("%d", tcpPort),
		"attach", "-r", "127.0.0.1", "-b", granted.BusID)
	output, err := cmd.CombinedOutput()
	outputStr := strings.TrimSpace(string(output))
	if err != nil {
		// Close listener to unblock Accept goroutine
		listener.Close()
		return nil, -1, fmt.Errorf("usbip.exe attach failed: %w (output: %s)", err, outputStr)
	}

	log.Printf("[vhci-win] usbip.exe output: %s", outputStr)

	// Parse VHCI port from usbip.exe output (e.g. "succesfully attached to port 0")
	vhciPort := parsePortFromOutput(outputStr)

	// Wait for management phase to complete
	result := <-resultCh
	if result.err != nil {
		return nil, -1, result.err
	}

	log.Printf("[vhci-win] device attached on VHCI port %d", vhciPort)
	return result.conn, vhciPort, nil
}

// handleImportRequest reads OP_REQ_IMPORT from the usbip.exe client
// and responds with OP_REP_IMPORT containing the device descriptor.
func handleImportRequest(conn net.Conn, granted *protocol.DeviceGranted, devInfo *RemoteDevice) error {
	// Read the OpHeader (8 bytes)
	hdr, err := usbip.ReadOpHeader(conn)
	if err != nil {
		return fmt.Errorf("reading op header: %w", err)
	}

	if hdr.Command != usbip.OpReqImport {
		return fmt.Errorf("unexpected command: 0x%04x (expected OP_REQ_IMPORT 0x%04x)", hdr.Command, usbip.OpReqImport)
	}

	// Read the 32-byte bus ID
	var busIDBuf [32]byte
	if _, err := io.ReadFull(conn, busIDBuf[:]); err != nil {
		return fmt.Errorf("reading bus ID: %w", err)
	}

	requestedBusID := usbip.GetBusID(busIDBuf)
	log.Printf("[vhci-win] OP_REQ_IMPORT for bus ID: %s", requestedBusID)

	// Build device descriptor from available info
	desc := buildDeviceDescriptor(granted, devInfo)

	// Build and send OP_REP_IMPORT reply
	reply, err := usbip.BuildImportReply(0, &desc)
	if err != nil {
		return fmt.Errorf("building import reply: %w", err)
	}

	if _, err := conn.Write(reply); err != nil {
		return fmt.Errorf("writing import reply: %w", err)
	}

	return nil
}

// buildDeviceDescriptor creates a USB/IP DeviceDescriptor from the
// information available in the DeviceGranted message and RemoteDevice.
func buildDeviceDescriptor(granted *protocol.DeviceGranted, devInfo *RemoteDevice) usbip.DeviceDescriptor {
	var desc usbip.DeviceDescriptor

	usbip.SetBusID(&desc.BusID, granted.BusID)
	usbip.SetPath(&desc.Path, "/sys/bus/usb/"+granted.BusID)

	desc.Speed = granted.Speed
	desc.BusNum = granted.DevID >> 16
	desc.DevNum = granted.DevID & 0xFFFF

	// Fill from RemoteDevice if available
	if devInfo != nil {
		desc.BusNum = devInfo.BusNum
		desc.DevNum = devInfo.DevNum

		// Parse hex VendorID/ProductID
		if vid, err := strconv.ParseUint(devInfo.VendorID, 16, 16); err == nil {
			desc.IDVendor = uint16(vid)
		}
		if pid, err := strconv.ParseUint(devInfo.ProductID, 16, 16); err == nil {
			desc.IDProduct = uint16(pid)
		}

		desc.BDeviceClass = devInfo.Class
		desc.BDeviceSubClass = devInfo.SubClass
		desc.BDeviceProtocol = devInfo.Protocol
		desc.BNumInterfaces = devInfo.NumInterfaces
	}

	// Defaults for fields not available in the protocol
	desc.BcdDevice = 0x0100
	desc.BConfigurationValue = 1
	desc.BNumConfigurations = 1

	if desc.BNumInterfaces == 0 {
		desc.BNumInterfaces = 1
	}

	return desc
}

// parsePortFromOutput extracts the VHCI port number from usbip.exe output.
// Returns -1 if the port cannot be parsed.
func parsePortFromOutput(output string) int {
	// Match patterns like "port 0", "port 1", etc.
	re := regexp.MustCompile(`(?i)port\s+(\d+)`)
	matches := re.FindStringSubmatch(output)
	if len(matches) >= 2 {
		if port, err := strconv.Atoi(matches[1]); err == nil {
			return port
		}
	}
	return -1
}

// createSocketPair is not used on Windows but required for compilation.
func createSocketPair() ([2]int, error) {
	return [2]int{}, fmt.Errorf("socketpair not available on Windows")
}

func closeFDs(fds [2]int) {}

func fdToFile(fd int, name string) *os.File {
	return nil
}

// logVHCIDeviceStatus is Linux-only (sysfs).
func logVHCIDeviceStatus(port int) {}

// fixVHCIDevicePermissions is not needed on Windows.
func fixVHCIDevicePermissions(port int) {}
