package client

import (
	"fmt"
	"io"
	"log"
	"sync"
	"time"

	"github.com/duffy/usb-server/internal/config"
	"github.com/duffy/usb-server/internal/crypto"
	"github.com/duffy/usb-server/internal/protocol"
	"github.com/duffy/usb-server/internal/usb"
	"github.com/duffy/usb-server/internal/usbip"
	"github.com/google/uuid"
)

// ShareManager handles sharing USB devices
type ShareManager struct {
	client  *Client
	cfg     *config.Config
	mu      sync.RWMutex
	devices []usb.Device
	active  map[string]*activeShare // busID -> active share
	tunnels map[string]*shareTunnel // tunnelID -> tunnel

	// secret derives per-tunnel keys. Nil when the client is configured with
	// only a group hash, in which case tunnels stay unencrypted.
	secret *crypto.TunnelSecret

	// listener accepts direct connections from peers. Nil when direct
	// connections are disabled or could not be set up, which just means every
	// tunnel goes through the relay.
	listener *directListener
}

type activeShare struct {
	device   *usb.Device
	server   *usbip.Server
	usedBy   string // client ID using this device
	tunnelID string
}

type shareTunnel struct {
	id    string
	busID string

	// in carries peer -> USB/IP server bytes. It buffers instead of blocking
	// so that feeding it from the WebSocket read loop cannot stall the client.
	in *streamBuffer

	// out carries USB/IP server -> peer bytes. This direction stays a pipe:
	// blocking there is real backpressure onto the USB reap loop, which is
	// what we want when the network cannot keep up.
	outPipe *io.PipeReader
	done    chan struct{}

	// codec seals outgoing and opens incoming payloads, whichever transport
	// carries them.
	codec *tunnelCodec

	// sendMu guards swapping the transport when a direct connection takes
	// over from the relay mid-tunnel.
	sendMu sync.Mutex
	send   tunnelSender
	direct *directConn
}

// setTransport switches this tunnel to a new sender, closing the old direct
// connection if there was one.
func (t *shareTunnel) setTransport(sender tunnelSender, conn *directConn) {
	t.sendMu.Lock()
	defer t.sendMu.Unlock()
	if t.direct != nil && t.direct != conn {
		t.direct.Close()
	}
	t.send = sender
	t.direct = conn
}

// deliver encodes and transmits one payload over the current transport.
func (t *shareTunnel) deliver(payload []byte) error {
	t.sendMu.Lock()
	sender := t.send
	t.sendMu.Unlock()

	if sender == nil {
		return fmt.Errorf("tunnel %s has no transport", t.id)
	}
	return send(t.codec, sender, payload)
}

// closeDirect tears down any direct connection this tunnel holds.
func (t *shareTunnel) closeDirect() {
	t.sendMu.Lock()
	defer t.sendMu.Unlock()
	if t.direct != nil {
		t.direct.Close()
		t.direct = nil
	}
}

// NewShareManager creates a share manager
func NewShareManager(client *Client, cfg *config.Config) *ShareManager {
	sm := &ShareManager{
		client:  client,
		cfg:     cfg,
		active:  make(map[string]*activeShare),
		tunnels: make(map[string]*shareTunnel),
		secret:  client.TunnelSecret(),
	}

	sm.startDirectListener()

	// Share-side messages only this manager handles.
	client.OnRequestDevice = sm.handleRequestDevice
	client.OnReleaseDevice = sm.handleReleaseDevice
	client.OnForceRelease = sm.handleForceRelease

	// Shared with the use manager in "both" mode, hence multicast.
	client.AddTunnelHandler(sm.handleTunnelData)
	client.AddClientLeftHandler(sm.handleClientLeft)
	client.AddDisconnectHandler(sm.handleRelayDisconnect)

	return sm
}

// startDirectListener opens the port peers connect to for direct tunnels.
//
// Failure is never fatal: without a listener every tunnel simply goes through
// the relay, which is exactly how the system worked before.
func (sm *ShareManager) startDirectListener() {
	if sm.cfg.DisableDirect {
		log.Printf("[share] direct connections disabled by configuration")
		return
	}
	if sm.secret == nil {
		log.Printf("[share] direct connections unavailable: no tokens configured, only a group hash")
		return
	}

	listener, err := newDirectListener(sm.cfg.DirectPort, sm.secret)
	if err != nil {
		log.Printf("[share] direct connections unavailable: %v (falling back to relay)", err)
		return
	}

	sm.listener = listener
	sm.client.SetDirectPort(listener.Port())
}

// DirectPort reports the port peers can reach for direct tunnels, 0 if none.
func (sm *ShareManager) DirectPort() int {
	if sm.listener == nil {
		return 0
	}
	return sm.listener.Port()
}

// awaitDirect waits for the peer to connect directly and, when it does, moves
// the tunnel off the relay.
//
// The switch is safe at any moment because USB/IP is a stream of complete
// messages and each tunnel frame carries one chunk of it: frames sent before
// the switch travel via the relay, frames after it travel directly, and both
// arrive in order at the same reader. Nothing is in flight in pieces.
func (sm *ShareManager) awaitDirect(tunnel *shareTunnel, accepted <-chan *directConn) {
	select {
	case conn := <-accepted:
		if conn == nil {
			return
		}

		select {
		case <-tunnel.done:
			conn.Close()
			return
		default:
		}

		log.Printf("[share] tunnel %s now direct with %s, bypassing the relay",
			tunnel.id, conn.RemoteAddr())
		tunnel.setTransport(directSender(conn), conn)

		// Incoming frames now arrive on this connection instead of the relay.
		receiveLoop(conn, tunnel.codec, func(payload []byte) error {
			_, err := tunnel.in.Write(payload)
			return err
		}, tunnel.done, "share/"+tunnel.busID)

		// The direct connection ended. The USB/IP stream cannot resume on the
		// relay mid-conversation — the peer's VHCI has torn down its side —
		// so release the device and let it be requested again.
		select {
		case <-tunnel.done:
		default:
			log.Printf("[share] direct connection for %s ended, releasing device", tunnel.busID)
			go sm.handleReleaseDevice(tunnel.busID, "")
		}

	case <-tunnel.done:
	}
}

// handleRelayDisconnect releases every active share after the relay link
// drops. The relay discarded those tunnels, so the remote side is gone and
// the local device would otherwise stay claimed and unusable.
func (sm *ShareManager) handleRelayDisconnect() {
	sm.mu.RLock()
	busIDs := make([]string, 0, len(sm.active))
	for busID := range sm.active {
		busIDs = append(busIDs, busID)
	}
	sm.mu.RUnlock()

	for _, busID := range busIDs {
		log.Printf("[share] releasing %s (relay connection lost)", busID)
		sm.handleReleaseDevice(busID, "")
	}
}

// Run starts the share manager: periodic device enumeration + event handling
func (sm *ShareManager) Run() error {
	// Initial enumeration
	sm.refreshDevices()
	sm.broadcastDeviceList()

	// Periodic refresh
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			sm.refreshDevices()
			sm.broadcastDeviceList()
		case <-sm.client.ctx.Done():
			sm.cleanup()
			return nil
		}
	}
}

// GetDevices returns the current device list
func (sm *ShareManager) GetDevices() []usb.Device {
	sm.mu.RLock()
	defer sm.mu.RUnlock()
	result := make([]usb.Device, len(sm.devices))
	copy(result, sm.devices)
	return result
}

// RefreshNow re-enumerates and announces immediately, rather than waiting for
// the next poll. Used when devices appear through the bridge, where the
// change is known the instant it happens.
func (sm *ShareManager) RefreshNow() {
	sm.refreshDevices()
	sm.broadcastDeviceList()
}

func (sm *ShareManager) refreshDevices() {
	devices, err := usb.Enumerate()
	if err != nil {
		log.Printf("[share] USB enumeration error: %v", err)
		return
	}

	sm.mu.Lock()
	sm.devices = devices
	sm.mu.Unlock()
}

func (sm *ShareManager) broadcastDeviceList() {
	sm.mu.RLock()
	defer sm.mu.RUnlock()

	var protoDevices []protocol.USBDevice
	for _, dev := range sm.devices {
		status := protocol.StatusAvailable
		usedBy := ""
		if share, ok := sm.active[dev.BusID]; ok {
			status = protocol.StatusInUse
			usedBy = share.usedBy
		}

		protoDevices = append(protoDevices, protocol.USBDevice{
			BusID:         dev.BusID,
			BusNum:        dev.BusNum,
			DevNum:        dev.DevNum,
			Speed:         dev.Speed,
			VendorID:      fmt.Sprintf("%04x", dev.VendorID),
			ProductID:     fmt.Sprintf("%04x", dev.ProductID),
			Class:         dev.DeviceClass,
			SubClass:      dev.DeviceSubClass,
			Protocol:      dev.DeviceProtocol,
			Name:          dev.DisplayName(),
			Manufacturer:  dev.Manufacturer,
			NumInterfaces: uint8(len(dev.Interfaces)),
			Status:        status,
			UsedBy:        usedBy,
		})
	}

	msg := &protocol.DeviceList{
		Type:             protocol.MsgDeviceList,
		ClientID:         sm.client.ID(),
		ClientName:       sm.client.Config().Name,
		Devices:          protoDevices,
		AllowForceDetach: sm.cfg.AllowForceDetach,
	}

	sm.client.SendJSON(msg)
}

func (sm *ShareManager) handleRequestDevice(targetClient, fromClient, busID, requestID string) {
	log.Printf("[share] device request: busID=%s from=%s", busID, fromClient)

	sm.mu.Lock()

	// Check if device exists
	var dev *usb.Device
	for i := range sm.devices {
		if sm.devices[i].BusID == busID {
			dev = &sm.devices[i]
			break
		}
	}

	if dev == nil {
		sm.mu.Unlock()
		sm.client.SendJSON(map[string]interface{}{
			"type":          protocol.MsgDeviceDenied,
			"bus_id":        busID,
			"request_id":    requestID,
			"reason":        "device not found",
			"target_client": fromClient,
		})
		return
	}

	// Check if already in use
	if _, inUse := sm.active[busID]; inUse {
		sm.mu.Unlock()
		sm.client.SendJSON(map[string]interface{}{
			"type":          protocol.MsgDeviceDenied,
			"bus_id":        busID,
			"request_id":    requestID,
			"reason":        "device already in use",
			"target_client": fromClient,
		})
		return
	}

	// Create USB/IP server for this device
	server := usbip.NewServer(dev)
	if err := server.Attach(); err != nil {
		sm.mu.Unlock()
		log.Printf("[share] failed to attach device %s: %v", busID, err)
		sm.client.SendJSON(map[string]interface{}{
			"type":          protocol.MsgDeviceDenied,
			"bus_id":        busID,
			"request_id":    requestID,
			"reason":        fmt.Sprintf("attach failed: %v", err),
			"target_client": fromClient,
		})
		return
	}

	tunnelID := uuid.New().String()[:16] // 16 chars for tunnel header
	for len(tunnelID) < 16 {
		tunnelID += "0"
	}

	codec, err := newTunnelCodec(sm.secret, tunnelID, crypto.DirShareToUse)
	if err != nil {
		sm.mu.Unlock()
		server.Detach()
		log.Printf("[share] failed to set up tunnel encryption for %s: %v", busID, err)
		sm.client.SendJSON(map[string]interface{}{
			"type":          protocol.MsgDeviceDenied,
			"bus_id":        busID,
			"request_id":    requestID,
			"reason":        fmt.Sprintf("tunnel setup failed: %v", err),
			"target_client": fromClient,
		})
		return
	}

	in := newStreamBuffer()
	outReader, outWriter := io.Pipe()

	tunnel := &shareTunnel{
		id:      tunnelID,
		busID:   busID,
		in:      in,
		outPipe: outReader,
		done:    make(chan struct{}),
		codec:   codec,
	}
	// Start on the relay. If the peer reaches us directly, the transport is
	// swapped underneath without the USB/IP layer noticing.
	tunnel.setTransport(relaySender(sm.client, tunnelID), nil)

	// Register the tunnel before announcing it, so a peer that connects
	// immediately after receiving the grant is not rejected as unknown.
	var accepted <-chan *directConn
	if sm.listener != nil {
		var err error
		if accepted, err = sm.listener.Expect(tunnelID); err != nil {
			log.Printf("[share] cannot expect a direct connection for %s: %v", busID, err)
		}
	}

	share := &activeShare{
		device:   dev,
		server:   server,
		usedBy:   fromClient,
		tunnelID: tunnelID,
	}

	sm.active[busID] = share
	sm.tunnels[tunnelID] = tunnel
	sm.mu.Unlock()

	// Start USB/IP protocol handler in background.
	// The tunnel carries the USB/IP transfer phase directly: on Linux the
	// use side hands the socket straight to VHCI, and on Windows usbip.exe's
	// management phase is answered locally, so there is no import request here.
	go func() {
		defer func() {
			close(tunnel.done)
			in.Close()
			outWriter.Close()
			outReader.Close()
		}()

		err := server.HandleConnection(in, outWriter)
		if err != nil {
			log.Printf("[share] USB/IP connection error for %s: %v", busID, err)
		}
	}()

	// Forward outgoing data from the USB/IP server over whichever transport
	// the tunnel currently uses.
	go func() {
		buf := make([]byte, 65536)
		for {
			n, err := outReader.Read(buf)
			if err != nil {
				return
			}
			if err := tunnel.deliver(buf[:n]); err != nil {
				log.Printf("[share] tunnel %s send failed: %v", tunnelID, err)
				return
			}
		}
	}()

	// Wait in the background for the peer to connect directly.
	if accepted != nil {
		go sm.awaitDirect(tunnel, accepted)
	}

	// Send grant message, including where we can be reached directly. The
	// relay adds the public address it sees before passing this on.
	sm.client.SendJSON(map[string]interface{}{
		"type":          protocol.MsgDeviceGranted,
		"bus_id":        busID,
		"tunnel_id":     tunnelID,
		"request_id":    requestID,
		"dev_id":        dev.DevID(),
		"speed":         dev.Speed,
		"target_client": fromClient,
		"endpoints":     localEndpoints(sm.DirectPort()),
		"encrypted":     codec.encrypted(),
	})

	log.Printf("[share] device %s granted to %s (tunnel=%s, encrypted=%v)",
		busID, fromClient, tunnelID, codec.encrypted())

	// Broadcast updated device list
	sm.refreshDevices()
	sm.broadcastDeviceList()
}

func (sm *ShareManager) handleReleaseDevice(busID, fromClient string) {
	log.Printf("[share] device release: busID=%s from=%s", busID, fromClient)

	sm.mu.Lock()
	share, exists := sm.active[busID]
	if !exists {
		sm.mu.Unlock()
		return
	}

	// Close the tunnel input to signal HandleConnection to stop reading
	var tunnelDone <-chan struct{}
	if tunnel, ok := sm.tunnels[share.tunnelID]; ok {
		tunnel.in.Close()
		tunnel.closeDirect()
		tunnelDone = tunnel.done
		delete(sm.tunnels, share.tunnelID)
	}
	if sm.listener != nil {
		sm.listener.Forget(share.tunnelID)
	}

	server := share.server
	delete(sm.active, busID)
	sm.mu.Unlock()

	// Wait for HandleConnection goroutine to finish before detaching.
	// This ensures no more URBs are being submitted when we detach.
	if tunnelDone != nil {
		<-tunnelDone
		log.Printf("[share] HandleConnection goroutine finished for %s", busID)
	}

	// Now safe to detach - no more USB/IP protocol processing
	server.Detach()

	// Notify client
	sm.client.SendJSON(&protocol.DeviceReleased{
		Type:     protocol.MsgDeviceReleased,
		BusID:    busID,
		ClientID: sm.client.ID(),
	})

	log.Printf("[share] device %s released", busID)

	// Refresh device list
	sm.refreshDevices()
	sm.broadcastDeviceList()
}

func (sm *ShareManager) handleForceRelease(targetClient, fromClient, busID string) {
	if !sm.cfg.AllowForceDetach {
		log.Printf("[share] force-release denied for %s (not allowed by config)", busID)
		return
	}

	sm.mu.RLock()
	share, exists := sm.active[busID]
	sm.mu.RUnlock()

	if !exists {
		return
	}

	log.Printf("[share] force-releasing %s (requested by %s, was used by %s)", busID, protocol.ShortID(fromClient), protocol.ShortID(share.usedBy))
	sm.handleReleaseDevice(busID, share.usedBy)
}

func (sm *ShareManager) handleClientLeft(msg *protocol.ClientLeft) {
	sm.mu.RLock()
	var toRelease []string
	for busID, share := range sm.active {
		if share.usedBy == msg.ClientID {
			toRelease = append(toRelease, busID)
		}
	}
	sm.mu.RUnlock()

	for _, busID := range toRelease {
		log.Printf("[share] auto-releasing %s (client %s left)", busID, protocol.ShortID(msg.ClientID))
		sm.handleReleaseDevice(busID, msg.ClientID)
	}
}

func (sm *ShareManager) handleTunnelData(tunnelID string, data []byte) {
	sm.mu.RLock()
	tunnel, exists := sm.tunnels[tunnelID]
	sm.mu.RUnlock()

	if !exists {
		// In "both" mode the use manager owns the other tunnels and sees the
		// same frames, so an unknown ID here is normal.
		return
	}

	payload, err := tunnel.codec.decode(data)
	if err != nil {
		log.Printf("[share] tunnel %s: rejecting relayed frame: %v", tunnelID, err)
		go sm.handleReleaseDevice(tunnel.busID, "")
		return
	}

	// Buffered, non-blocking: this runs on the WebSocket read loop, which
	// must never stall on the USB side.
	if _, err := tunnel.in.Write(payload); err != nil {
		log.Printf("[share] tunnel %s input failed: %v — releasing %s", tunnelID, err, tunnel.busID)
		go sm.handleReleaseDevice(tunnel.busID, "")
	}
}

func (sm *ShareManager) cleanup() {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	for busID, share := range sm.active {
		if tunnel, ok := sm.tunnels[share.tunnelID]; ok {
			tunnel.in.Close()
			tunnel.closeDirect()
		}
		share.server.Detach()
		log.Printf("[share] cleaned up device %s", busID)
	}

	sm.active = make(map[string]*activeShare)
	sm.tunnels = make(map[string]*shareTunnel)

	if sm.listener != nil {
		sm.listener.Close()
		sm.listener = nil
	}
}

// DeviceListForAPI returns device info formatted for the web API
func (sm *ShareManager) DeviceListForAPI() []map[string]interface{} {
	sm.mu.RLock()
	defer sm.mu.RUnlock()

	var result []map[string]interface{}
	for _, dev := range sm.devices {
		status := "available"
		usedBy := ""
		if share, ok := sm.active[dev.BusID]; ok {
			status = "in_use"
			usedBy = share.usedBy
		}

		result = append(result, map[string]interface{}{
			"bus_id":     dev.BusID,
			"vendor_id":  fmt.Sprintf("%04x", dev.VendorID),
			"product_id": fmt.Sprintf("%04x", dev.ProductID),
			"name":       dev.DisplayName(),
			"status":     status,
			"used_by":    usedBy,
			"speed":      dev.Speed,
		})
	}
	return result
}
