// SPDX-License-Identifier: 0BSD

package interfaces

import (
	"encoding/binary"
	"fmt"
	"time"

	"git.quad4.io/Networks/Reticulum-Go/pkg/common"
	"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
)

const (
	RNODE_CMD_DATA        = 0x00
	RNODE_CMD_FREQUENCY   = 0x01
	RNODE_CMD_BANDWIDTH   = 0x02
	RNODE_CMD_TXPOWER     = 0x03
	RNODE_CMD_SF          = 0x04
	RNODE_CMD_CR          = 0x05
	RNODE_CMD_RADIO_STATE = 0x06
	RNODE_CMD_RADIO_LOCK  = 0x07
	RNODE_CMD_DETECT      = 0x08
	RNODE_CMD_LEAVE       = 0x0A
	RNODE_CMD_ST_ALOCK    = 0x0B
	RNODE_CMD_LT_ALOCK    = 0x0C
	RNODE_CMD_READY       = 0x0F
	RNODE_CMD_STAT_RX     = 0x21
	RNODE_CMD_STAT_TX     = 0x22
	RNODE_CMD_STAT_RSSI   = 0x23
	RNODE_CMD_STAT_SNR    = 0x24
	RNODE_CMD_FW_VERSION  = 0x50
	RNODE_CMD_PLATFORM    = 0x48
	RNODE_CMD_MCU         = 0x49

	RNODE_DETECT_REQ  = 0x73
	RNODE_DETECT_RESP = 0x46

	RNODE_RSSI_OFFSET = 157
)

// RNodeInterface represents a Reticulum node interface.
type RNodeInterface struct {
	Interface
	frequency uint32
	bandwidth uint32
	sf        uint8
	cr        uint8
	txPower   uint8
	callback  common.PacketCallback

	rFrequency uint32
	rBandwidth uint32
	rTXPower   uint8
	rSF        uint8
	rCR        uint8
	rState     uint8
	rDetected  bool
	rMajVer    uint8
	rMinVer    uint8

	interfaceReady bool
	packetQueue    [][]byte
}

// NewRNodeInterface creates a new RNodeInterface.
func NewRNodeInterface(name string, underlying Interface, freq uint32, bw uint32, sf uint8, cr uint8, txPower uint8) (*RNodeInterface, error) {
	ri := &RNodeInterface{
		Interface: underlying,
		frequency: freq,
		bandwidth: bw,
		sf:        sf,
		cr:        cr,
		txPower:   txPower,
	}

	underlying.SetPacketCallback(ri.handleIncoming)
	return ri, nil
}

// SetPacketCallback sets the packet callback for the RNodeInterface.
func (ri *RNodeInterface) SetPacketCallback(cb common.PacketCallback) {
	ri.callback = cb
}

func (ri *RNodeInterface) handleIncoming(data []byte, ni common.NetworkInterface) {
	if len(data) < 1 {
		return
	}

	cmd := data[0]
	payload := data[1:]

	switch cmd {
	case RNODE_CMD_DATA:
		if ri.callback != nil {
			ri.callback(payload, ri)
		}
	case RNODE_CMD_READY:
		ri.processQueue()
	case RNODE_CMD_DETECT:
		if len(payload) >= 1 && payload[0] == RNODE_DETECT_RESP {
			ri.rDetected = true
		}
	case RNODE_CMD_FW_VERSION:
		if len(payload) >= 2 {
			ri.rMajVer = payload[0]
			ri.rMinVer = payload[1]
			debug.Log(debug.DEBUG_INFO, "RNode firmware version", "name", ri.GetName(), "version", fmt.Sprintf("%d.%d", ri.rMajVer, ri.rMinVer))
		}
	case RNODE_CMD_FREQUENCY:
		if len(payload) >= 4 {
			ri.rFrequency = binary.BigEndian.Uint32(payload)
		}
	case RNODE_CMD_BANDWIDTH:
		if len(payload) >= 4 {
			ri.rBandwidth = binary.BigEndian.Uint32(payload)
		}
	case RNODE_CMD_TXPOWER:
		if len(payload) >= 1 {
			ri.rTXPower = payload[0]
		}
	case RNODE_CMD_SF:
		if len(payload) >= 1 {
			ri.rSF = payload[0]
		}
	case RNODE_CMD_CR:
		if len(payload) >= 1 {
			ri.rCR = payload[0]
		}
	case RNODE_CMD_RADIO_STATE:
		if len(payload) >= 1 {
			ri.rState = payload[0]
		}
	case RNODE_CMD_STAT_RSSI:
		if len(payload) >= 1 {
			rssi := int(payload[0]) - RNODE_RSSI_OFFSET
			debug.Log(debug.DEBUG_VERBOSE, "RNode RSSI", "name", ri.GetName(), "rssi", rssi)
		}
	case RNODE_CMD_STAT_SNR:
		if len(payload) >= 1 {
			snr := float32(int8(payload[0])) * 0.25
			debug.Log(debug.DEBUG_VERBOSE, "RNode SNR", "name", ri.GetName(), "snr", snr)
		}
	default:
		debug.Log(debug.DEBUG_ALL, "RNode received command", "cmd", fmt.Sprintf("0x%02x", cmd), "len", len(payload))
	}
}

func (ri *RNodeInterface) processQueue() {
	ri.interfaceReady = true
	if len(ri.packetQueue) > 0 {
		packet := ri.packetQueue[0]
		ri.packetQueue = ri.packetQueue[1:]
		_ = ri.Send(packet, "")
	}
}

// Start initializes the RNodeInterface and configures device parameters.
func (ri *RNodeInterface) Start() error {
	err := ri.Interface.Start()
	if err != nil {
		return err
	}

	time.Sleep(2 * time.Second)

	if err := ri.detect(); err != nil {
		return err
	}

	debug.Log(debug.DEBUG_INFO, "Initializing RNode...", "name", ri.GetName())

	if ri.frequency != 0 {
		freqBytes := make([]byte, 4)
		binary.BigEndian.PutUint32(freqBytes, ri.frequency)
		if err := ri.sendRNodeCommand(RNODE_CMD_FREQUENCY, freqBytes); err != nil {
			return err
		}
	}

	if ri.bandwidth != 0 {
		bwBytes := make([]byte, 4)
		binary.BigEndian.PutUint32(bwBytes, ri.bandwidth)
		if err := ri.sendRNodeCommand(RNODE_CMD_BANDWIDTH, bwBytes); err != nil {
			return err
		}
	}

	if ri.sf != 0 {
		if err := ri.sendRNodeCommand(RNODE_CMD_SF, []byte{ri.sf}); err != nil {
			return err
		}
	}

	if ri.cr != 0 {
		if err := ri.sendRNodeCommand(RNODE_CMD_CR, []byte{ri.cr}); err != nil {
			return err
		}
	}

	if ri.txPower != 0 {
		if err := ri.sendRNodeCommand(RNODE_CMD_TXPOWER, []byte{ri.txPower}); err != nil {
			return err
		}
	}

	if err := ri.sendRNodeCommand(RNODE_CMD_RADIO_STATE, []byte{0x01}); err != nil {
		return err
	}

	ri.interfaceReady = true

	debug.Log(debug.DEBUG_INFO, "RNode initialized", "name", ri.GetName())
	return nil
}

// detect attempts to detect the RNode device and obtain firmware version.
func (ri *RNodeInterface) detect() error {
	detectCmd := []byte{RNODE_DETECT_REQ}
	if err := ri.sendRNodeCommand(RNODE_CMD_DETECT, detectCmd); err != nil {
		return err
	}

	start := time.Now()
	for !ri.rDetected {
		if time.Since(start) > 2*time.Second {
			debug.Log(debug.DEBUG_ERROR, "RNode detection timed out", "name", ri.GetName())
			break
		}
		time.Sleep(100 * time.Millisecond)
	}

	if err := ri.sendRNodeCommand(RNODE_CMD_FW_VERSION, []byte{0x00}); err != nil {
		return err
	}

	return nil
}

// sendRNodeCommand sends a command to the RNode device.
func (ri *RNodeInterface) sendRNodeCommand(cmd byte, data []byte) error {
	if kissInterface, ok := ri.Interface.(interface {
		SendKISS(byte, []byte) error
	}); ok {
		return kissInterface.SendKISS(cmd, data)
	}

	frame := make([]byte, 0, len(data)+1)
	frame = append(frame, cmd)
	frame = append(frame, data...)
	return ri.Interface.Send(frame, "")
}

// Send transmits data using the underlying interface.
func (ri *RNodeInterface) Send(data []byte, addr string) error {
	if !ri.interfaceReady {
		ri.packetQueue = append(ri.packetQueue, data)
		return nil
	}
	return ri.Interface.Send(data, addr)
}
