package cat

import (
	"fmt"
	"strings"

	"hamlog/internal/applog"
)

// Satellite operation on a FlexRadio.
//
// A Flex has no satellite mode, and does not need one: it has slices. Slice A
// is the downlink and slice B the uplink — the arrangement every Flex satellite
// operator already uses by hand — with the transmitter on B and full duplex on,
// so the operator hears their own signal come back through the transponder.
// The transverters that put 145 and 435 MHz within the radio's reach are
// configured in SmartSDR, and their offsets are the radio's business: OpsLog
// sends the real satellite frequency and SmartSDR does the arithmetic.
//
// The two slices are CREATED when they are missing, because "slice B does not
// exist" is not a thing to make the operator fix at the start of a ten-minute
// pass. Only what OpsLog created is taken away again on disarming: a slice the
// operator opened is theirs.

// SetSatellite arranges (or unwinds) the two-slice satellite pair.
func (f *Flex) SetSatellite(on bool) error {
	f.mu.Lock()
	connected := f.conn != nil
	f.mu.Unlock()
	if !connected {
		return fmt.Errorf("flex: not connected")
	}
	if !on {
		return f.satDisarm()
	}

	// The downlink slice is the one the operator is already on: taking the
	// active slice rather than insisting on index 0 means arming the satellite
	// does not move them off the receiver they were listening to.
	f.mu.Lock()
	rxIdx, _ := f.mainSliceLocked()
	var txIdx = -1
	for _, idx := range f.sortedSliceIdxLocked() {
		if s := f.slices[idx]; s != nil && s.inUse && idx != rxIdx {
			txIdx = idx
			break
		}
	}
	f.satRX, f.satTX = rxIdx, txIdx
	f.satOn = true
	f.mu.Unlock()

	// Full duplex before anything else: without it the radio mutes the receiver
	// on transmit, and an operator who cannot hear their own downlink has no way
	// to know they are in the passband at all.
	f.send("radio set full_duplex_enabled=1")

	if rxIdx < 0 {
		// A radio with no slice at all. One is created; the status that comes
		// back adopts it as the downlink.
		f.satCreate("rx", 145.900, "USB")
	}
	if txIdx < 0 {
		f.satCreate("tx", 435.100, "USB")
	} else {
		f.send(fmt.Sprintf("slice s %d tx=1", txIdx))
	}
	applog.Printf("flex: satellite armed (rx slice %d, tx slice %d)", rxIdx, txIdx)
	return nil
}

func (f *Flex) satDisarm() error {
	f.mu.Lock()
	rx, tx, created := f.satRX, f.satTX, f.satCreatedTX
	f.satOn, f.satRX, f.satTX, f.satCreatedTX = false, -1, -1, false
	f.mu.Unlock()

	f.send("radio set full_duplex_enabled=0")
	if created && tx >= 0 {
		f.send(fmt.Sprintf("slice remove %d", tx))
	}
	// Transmit goes back where the operator is listening. A radio left
	// transmitting on a slice that no longer exists — or on the uplink band with
	// the satellite gone — is not somewhere anyone should be handed back.
	if rx >= 0 {
		f.send(fmt.Sprintf("slice s %d tx=1", rx))
	}
	applog.Printf("flex: satellite disarmed")
	return nil
}

// satCreate asks for a slice and remembers what it is for; the index arrives in
// the reply (see the R-line handler), which is where the role is applied.
func (f *Flex) satCreate(role string, freqMHz float64, mode string) {
	seq := f.send(fmt.Sprintf("slice create freq=%.6f mode=%s", freqMHz, mode))
	if seq <= 0 {
		return
	}
	f.mu.Lock()
	if f.pendingSat == nil {
		f.pendingSat = map[int]string{}
	}
	f.pendingSat[seq] = role
	f.mu.Unlock()
}

// adoptSatSlice records a freshly created slice in its role. Called from the
// reply handler with the index the radio assigned.
func (f *Flex) adoptSatSlice(role string, idx int) {
	f.mu.Lock()
	switch role {
	case "rx":
		f.satRX = idx
	case "tx":
		f.satTX = idx
		f.satCreatedTX = true
	}
	f.mu.Unlock()
	if role == "tx" {
		f.send(fmt.Sprintf("slice s %d tx=1", idx))
	}
	applog.Printf("flex: satellite %s slice is %d", role, idx)
}

// TuneSatellite moves the two slices.
func (f *Flex) TuneSatellite(downHz, upHz int64, downMode, upMode string) error {
	f.mu.Lock()
	rx, tx := f.satRX, f.satTX
	connected := f.conn != nil
	if rx >= 0 && f.slices[rx] != nil && downHz > 0 {
		f.slices[rx].freqHz = downHz // optimistic, as SetFrequency is
	}
	if tx >= 0 && f.slices[tx] != nil && upHz > 0 {
		f.slices[tx].freqHz = upHz
	}
	f.mu.Unlock()
	if !connected {
		return fmt.Errorf("flex: not connected")
	}
	if rx < 0 {
		// The slice was asked for and its index has not come back yet. Nothing is
		// wrong — the next Doppler step, a second later, will find it.
		return nil
	}
	if downHz > 0 {
		f.send(fmt.Sprintf("slice t %d %.6f", rx, float64(downHz)/1e6))
		f.satMode(rx, downMode, downHz)
	}
	if tx >= 0 && upHz > 0 {
		f.send(fmt.Sprintf("slice t %d %.6f", tx, float64(upHz)/1e6))
		f.satMode(tx, upMode, upHz)
	}
	return nil
}

// satMode sets a slice's mode only when it is not already there. A mode command
// on every Doppler step is a command a second per slice for a whole pass, and
// SmartSDR redraws the filter each time.
func (f *Flex) satMode(idx int, mode string, freqHz int64) {
	mode = strings.TrimSpace(mode)
	if mode == "" {
		return
	}
	// USB on both sides above 30 MHz, which is every satellite worth the name —
	// including the parts of a passband that would be an LSB band down on HF.
	if strings.EqualFold(mode, "SSB") && freqHz > 30_000_000 {
		mode = "USB"
	}
	fm := adifModeToFlex(mode, freqHz)
	if fm == "" {
		return
	}
	f.mu.Lock()
	s := f.slices[idx]
	same := s != nil && strings.EqualFold(s.mode, fm)
	if s != nil {
		s.mode = fm
	}
	f.mu.Unlock()
	if same {
		return
	}
	f.send(fmt.Sprintf("slice s %d mode=%s", idx, fm))
}

// SatReceiveHz is where the downlink slice sits.
//
// From the cache, not from a read: SmartSDR pushes every slice change as it
// happens, so the cached value is what the radio said, and there is no round
// trip to pay for once a second.
func (f *Flex) SatReceiveHz() (int64, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.satRX < 0 {
		return 0, fmt.Errorf("flex: no downlink slice")
	}
	s := f.slices[f.satRX]
	if s == nil || !s.inUse {
		return 0, fmt.Errorf("flex: the downlink slice has gone")
	}
	return s.freqHz, nil
}
