package skycat

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

// SatMode defines the satellite operating mode for SkyCAT.
type SatMode string

const (
	ModeDuplex  SatMode = "Duplex"
	ModeSplit   SatMode = "Split"
	ModeSimplex SatMode = "Simplex"
)

// Client manages a TCP connection to skycatd / rigctld.
// Protocol: simple newline-terminated text commands (HamLib-compatible).
//
// Key commands:
//
//	U Duplex        — satellite duplex mode (RX≠TX)
//	U Split         — split VFO mode
//	U Simplex       — simplex mode
//	F {hz}          — set RX frequency (Hz, integer)
//	I {hz}          — set TX frequency (Hz, integer)
//	M {mode} 0      — set RX mode (FM, USB, LSB, CW…)
//	X {mode} 0      — set TX mode
type Client struct {
	mu         sync.Mutex
	conn       net.Conn
	reader     *bufio.Reader
	connected  atomic.Bool
	satMode    SatMode
	lastDownHz float64
	lastUpHz   float64
}

func NewClient() *Client {
	return &Client{satMode: ModeDuplex}
}

// Connect establishes a TCP connection to skycatd (default port 4532).
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("SkyCAT connect %s: %w", addr, err)
	}

	c.conn = conn
	c.reader = bufio.NewReader(conn)
	c.connected.Store(true)

	// Start read loop to drain responses (skycatd sends RPRT lines)
	go c.readLoop()

	// Set satellite duplex mode immediately after connect
	log.Printf("[SkyCAT] Connected to %s", addr)
	c.sendLocked(fmt.Sprintf("U %s", c.satMode))
	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("[SkyCAT] Disconnected")
}

// IsConnected returns true if currently connected.
func (c *Client) IsConnected() bool {
	return c.connected.Load()
}

// SetSatMode configures the operating mode (Duplex/Split/Simplex).
// Sends the U command to skycatd.
func (c *Client) SetSatMode(mode SatMode) error {
	c.satMode = mode
	return c.sendCommand(fmt.Sprintf("U %s", mode))
}

// SetFrequency sends corrected RX and TX frequencies to skycatd.
// downHz = RX downlink (command F), upHz = TX uplink (command I).
// Dead-band: 1 Hz.
func (c *Client) SetFrequency(downHz, upHz float64) error {
	if !c.connected.Load() {
		return fmt.Errorf("not connected")
	}

	c.mu.Lock()
	defer c.mu.Unlock()

	var errs []error

	if downHz > 0 && absf(downHz-c.lastDownHz) >= 1.0 {
		if err := c.sendLocked(fmt.Sprintf("F %.0f", downHz)); err != nil {
			errs = append(errs, fmt.Errorf("RX freq: %w", err))
		} else {
			c.lastDownHz = downHz
		}
	}

	if upHz > 0 && absf(upHz-c.lastUpHz) >= 1.0 {
		if err := c.sendLocked(fmt.Sprintf("I %.0f", upHz)); err != nil {
			errs = append(errs, fmt.Errorf("TX freq: %w", err))
		} else {
			c.lastUpHz = upHz
		}
	}

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

// SetMode sets RX and TX modes (FM, USB, LSB, CW…).
func (c *Client) SetMode(mode string) error {
	mode = strings.ToUpper(strings.TrimSpace(mode))
	var errs []error
	if err := c.sendCommand(fmt.Sprintf("M %s 0", mode)); err != nil {
		errs = append(errs, fmt.Errorf("RX mode: %w", err))
	}
	if err := c.sendCommand(fmt.Sprintf("X %s 0", mode)); err != nil {
		errs = append(errs, fmt.Errorf("TX mode: %w", err))
	}
	if len(errs) > 0 {
		return fmt.Errorf("SkyCAT SetMode: %v", errs)
	}
	log.Printf("[SkyCAT] Mode → %s", mode)
	return nil
}

// ResetDeadband forces next frequency command to be sent immediately.
func (c *Client) ResetDeadband() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.lastDownHz = 0
	c.lastUpHz = 0
}

// SatModeToSkyCAT converts a satellite DB mode string to SkyCAT mode string.
func SatModeToSkyCAT(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 "PKTLSB"
	case "AM":
		return "AM"
	default:
		return "USB"
	}
}

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

// sendLocked sends a command — caller must hold c.mu.
func (c *Client) sendLocked(cmd string) error {
	if c.conn == nil {
		return fmt.Errorf("not connected")
	}
	line := cmd + "\n"
	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("send: %w", err)
	}
	log.Printf("[SkyCAT] → %s", strings.TrimSpace(line))
	return nil
}

// readLoop drains responses from skycatd (RPRT 0 = OK, RPRT -1 = error).
func (c *Client) readLoop() {
	for {
		line, err := c.reader.ReadString('\n')
		if err != nil {
			if c.connected.Load() {
				log.Printf("[SkyCAT] Read error: %v", err)
				c.connected.Store(false)
			}
			return
		}
		log.Printf("[SkyCAT] ← %s", strings.TrimSpace(line))
	}
}

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