package udp

import (
	"fmt"
	"strings"

	"hamlog/internal/applog"
)

// This file holds the outbound emitters: OpsLog → other programs over UDP.
// Formats are chosen per connection row (like the inbound parsers), so the user
// can point PstRotator, a second logger, an SDR, etc. at OpsLog.

// tensOfHz converts a frequency in Hz to the "tens of Hz" unit N1MM and
// PstRotator use for their frequency fields (e.g. 14 025 500 Hz → 1 402 550).
func tensOfHz(freqHz int64) int64 { return freqHz / 10 }

// BuildPstFreq builds the datagram PstRotatorAz expects for its "DXLog.net"
// tracker: <PST><FREQUENCY>{tens of Hz}</FREQUENCY></PST>. Verified by probing a
// live PstRotatorAz — it reads the value as tens of Hz (14025.5 kHz → 1402550 →
// displayed 3.5255 MHz for 3525.5 kHz, etc.).
func BuildPstFreq(freqHz int64) []byte {
	return []byte(fmt.Sprintf("<PST><FREQUENCY>%d</FREQUENCY></PST>", tensOfHz(freqHz)))
}

// BuildN1MMRadioInfo builds an N1MM Logger+ RadioInfo XML datagram. <Freq> and
// <TXFreq> are in tens of Hz. Consumed by PstRotator (as the "N1MM Logger"
// tracker) and many other programs. mode is passed through (CW/USB/LSB/…).
func BuildN1MMRadioInfo(station string, rxFreqHz, txFreqHz int64, mode, opCall string) []byte {
	if station == "" {
		station = "OPSLOG"
	}
	if txFreqHz == 0 {
		txFreqHz = rxFreqHz
	}
	var b strings.Builder
	b.WriteString(`<?xml version="1.0" encoding="utf-8"?>`)
	b.WriteString(`<RadioInfo>`)
	b.WriteString(`<StationName>` + xmlEsc(station) + `</StationName>`)
	b.WriteString(`<RadioNr>1</RadioNr>`)
	b.WriteString(fmt.Sprintf(`<Freq>%d</Freq>`, tensOfHz(rxFreqHz)))
	b.WriteString(fmt.Sprintf(`<TXFreq>%d</TXFreq>`, tensOfHz(txFreqHz)))
	b.WriteString(`<Mode>` + xmlEsc(mode) + `</Mode>`)
	b.WriteString(`<OpCall>` + xmlEsc(opCall) + `</OpCall>`)
	b.WriteString(`<IsRunning>True</IsRunning>`)
	b.WriteString(`<FocusEntry>0</FocusEntry>`)
	b.WriteString(`<Antenna>0</Antenna>`)
	b.WriteString(`<Rotors></Rotors>`)
	b.WriteString(`<FocusRadioNr>1</FocusRadioNr>`)
	b.WriteString(`<IsStereo>False</IsStereo>`)
	b.WriteString(`<ActiveRadioNr>1</ActiveRadioNr>`)
	b.WriteString(`</RadioInfo>`)
	return []byte(b.String())
}

func xmlEsc(s string) string {
	r := strings.NewReplacer("&", "&amp;", "<", "&lt;", ">", "&gt;", `"`, "&quot;", "'", "&apos;")
	return r.Replace(s)
}

// RadioState is a snapshot the app pushes to EmitRadioState on freq/mode change.
type RadioState struct {
	StationName string
	OpCall      string
	RxFreqHz    int64 // operating/RX frequency
	TxFreqHz    int64 // TX frequency (may equal RX when not split)
	Mode        string
}

// EmitRadioState sends the current radio frequency/mode to every enabled
// outbound row whose format is frequency-based (PstRotator, N1MM RadioInfo).
// Best-effort: send errors are logged, never returned to the caller.
func (m *Manager) EmitRadioState(st RadioState) {
	for _, c := range m.Outbound(ServicePstFreq) {
		m.sendTo(c, BuildPstFreq(st.RxFreqHz))
	}
	for _, c := range m.Outbound(ServiceN1MMRadio) {
		m.sendTo(c, BuildN1MMRadioInfo(st.StationName, st.RxFreqHz, st.TxFreqHz, st.Mode, st.OpCall))
	}
}

// EmitLoggedADIF sends the ADIF of a just-logged QSO to every enabled outbound
// "ADIF message" row (db_updated) — lets a second logger or Cloudlog gateway
// pick up contacts as they're logged.
func (m *Manager) EmitLoggedADIF(adif string) {
	if strings.TrimSpace(adif) == "" {
		return
	}
	for _, c := range m.Outbound(ServiceDBUpdated) {
		m.sendTo(c, []byte(adif))
	}
}

// sendTo resolves the row's destination (host:port) and fires one datagram.
func (m *Manager) sendTo(c Config, payload []byte) {
	host := strings.TrimSpace(c.DestinationIP)
	if host == "" {
		host = "127.0.0.1"
	}
	dst := fmt.Sprintf("%s:%d", host, c.Port)
	if err := SendUDP(dst, payload); err != nil {
		applog.Printf("udp: [%s] outbound send to %s failed: %v", c.Name, dst, err)
	}
}
