package flexradio

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

// Client manages a TCP connection to FlexRadio SmartSDR API.
// For satellite operation:
//   - Slice A (index 0) = RX downlink (436 MHz for SO-50)
//   - Slice B (index 1) = TX uplink (145 MHz for SO-50)
type Client struct {
	mu        sync.Mutex
	conn      net.Conn
	scanner   *bufio.Scanner
	connected atomic.Bool
	seqNum    uint32

	rxSlice    int     // Slice index for RX (downlink) — default 0 = Slice A
	txSlice    int     // Slice index for TX (uplink)  — default 1 = Slice B
	lastDownHz float64 // Last sent RX frequency (dead-band)
	lastUpHz   float64 // Last sent TX frequency (dead-band)
}

func NewClient() *Client {
	return &Client{rxSlice: 0, txSlice: 1} // defaults — always overridden by SetSliceConfig on connect
}

// Connect establishes TCP connection to FlexRadio SmartSDR API (port 4992).
func (c *Client) Connect(host string, port int) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.connected.Load() {
		c.disconnectLocked()
	}

	addr := fmt.Sprintf("%s:%d", host, port)
	conn, err := net.DialTimeout("tcp", addr, 5*time.Second)
	if err != nil {
		return fmt.Errorf("connexion FlexRadio %s : %w", addr, err)
	}

	c.conn = conn
	c.scanner = bufio.NewScanner(conn)
	c.connected.Store(true)

	go c.readLoop()

	log.Printf("[FlexRadio] Connecté à %s (RX=Slice %s, TX=Slice %s)",
		addr, sliceLetter(c.rxSlice), sliceLetter(c.txSlice))
	return nil
}

// Disconnect closes the connection.
func (c *Client) Disconnect() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.disconnectLocked()
}

func (c *Client) disconnectLocked() {
	if c.conn != nil {
		c.conn.Close()
		c.conn = nil
	}
	c.connected.Store(false)
	log.Println("[FlexRadio] Déconnecté")
}

func (c *Client) IsConnected() bool {
	return c.connected.Load()
}

// SetFrequency applies Doppler-corrected frequencies to both slices.
// downHz = corrected RX frequency → rxSlice (default Slice A)
// upHz   = corrected TX frequency → txSlice (default Slice B)
// Only sends command if frequency changed by more than 1 Hz (dead-band)
func (c *Client) SetFrequency(downHz, upHz float64) error {
	if !c.connected.Load() {
		return fmt.Errorf("non connecté")
	}

	var errs []error

	// RX downlink — use "slice t" (tune) command
	if downHz > 0 {
		downMHz := downHz / 1e6
		if abs(downHz-c.lastDownHz) >= 1.0 { // 1 Hz dead-band
			cmd := fmt.Sprintf("slice t %d %.6f", c.rxSlice, downMHz)
			if err := c.sendCommand(cmd); err != nil {
				errs = append(errs, err)
			} else {
				c.lastDownHz = downHz
			}
		}
	}

	// TX uplink — use "slice t" (tune) command
	if upHz > 0 {
		upMHz := upHz / 1e6
		if abs(upHz-c.lastUpHz) >= 1.0 {
			cmd := fmt.Sprintf("slice t %d %.6f", c.txSlice, upMHz)
			if err := c.sendCommand(cmd); err != nil {
				errs = append(errs, err)
			} else {
				c.lastUpHz = upHz
			}
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("FlexRadio SetFrequency: %v", errs)
	}
	return nil
}

// ResetDeadband forces next frequency command to be sent regardless of change.
func (c *Client) ResetDeadband() {
	c.lastDownHz = 0
	c.lastUpHz = 0
}

func abs(x float64) float64 {
	if x < 0 {
		return -x
	}
	return x
}

// SetSlices configures which slice indices to use for RX and TX.
// Default: rx=0 (Slice A), tx=1 (Slice B)
func (c *Client) SetSlices(rxIdx, txIdx int) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.rxSlice = rxIdx
	c.txSlice = txIdx
	log.Printf("[FlexRadio] Slices configurées: RX=Slice %s, TX=Slice %s",
		sliceLetter(rxIdx), sliceLetter(txIdx))
}

// GetSlices returns current RX and TX slice indices.
func (c *Client) GetSlices() (rx, tx int) {
	return c.rxSlice, c.txSlice
}

// QuerySlices sends a slice list request to discover available slices.
func (c *Client) QuerySlices() error {
	return c.sendCommand("slice list")
}

// SetMode sets the demodulation mode on RX and/or TX slices.
// mode: "usb", "lsb", "cw", "am", "sam", "fm", "nfm", "dfm", "digl", "digu", "rtty"
// For satellite: RX and TX usually have the same mode (FM for FM sats, LSB/USB for linear)
func (c *Client) SetMode(mode string) error {
	if !c.connected.Load() {
		return fmt.Errorf("not connected")
	}
	mode = strings.ToLower(strings.TrimSpace(mode))
	var errs []error
	// Set mode on RX slice
	if err := c.sendCommand(fmt.Sprintf("slice s %d mode=%s", c.rxSlice, mode)); err != nil {
		errs = append(errs, fmt.Errorf("RX mode: %w", err))
	}
	// Set mode on TX slice (same mode for satellite split operation)
	if err := c.sendCommand(fmt.Sprintf("slice s %d mode=%s", c.txSlice, mode)); err != nil {
		errs = append(errs, fmt.Errorf("TX mode: %w", err))
	}
	if len(errs) > 0 {
		return fmt.Errorf("SetMode: %v", errs)
	}
	log.Printf("[FlexRadio] Mode set to %s on slices %s/%s",
		strings.ToUpper(mode), sliceLetter(c.rxSlice), sliceLetter(c.txSlice))
	return nil
}

// SatModeToFlex converts a satellite DB mode string to FlexRadio mode.
// FM satellites use "fm", linear transponders use "lsb" or "usb".
func SatModeToFlex(satMode string) string {
	switch strings.ToUpper(strings.TrimSpace(satMode)) {
	case "FM", "NFM":
		return "fm"
	case "LSB":
		return "lsb"
	case "USB":
		return "usb"
	case "CW", "CW/DATA":
		return "cw"
	case "APRS", "DATA", "DIGI":
		return "digl"
	case "AM":
		return "am"
	default:
		return "usb" // safe default
	}
}

func (c *Client) sendCommand(cmd string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.conn == nil {
		return fmt.Errorf("non connecté")
	}

	seq := atomic.AddUint32(&c.seqNum, 1)
	line := fmt.Sprintf("C%d|%s\n", seq, cmd)

	c.conn.SetWriteDeadline(time.Now().Add(2 * time.Second))
	_, err := fmt.Fprint(c.conn, line)
	if err != nil {
		c.connected.Store(false)
		return fmt.Errorf("envoi commande: %w", err)
	}
	log.Printf("[FlexRadio] → %s", strings.TrimSpace(line))
	return nil
}

func (c *Client) readLoop() {
	for c.scanner.Scan() {
		line := c.scanner.Text()
		// Log ALL responses for debugging
		log.Printf("[FlexRadio] ← %s", line)
	}
	c.connected.Store(false)
	log.Println("[FlexRadio] Disconnected")
}

func sliceLetter(idx int) string {
	letters := []string{"A", "B", "C", "D", "E", "F", "G", "H"}
	if idx < len(letters) {
		return letters[idx]
	}
	return fmt.Sprintf("%d", idx)
}
