//go:build windows

package cat

import (
	"context"
	"net"
	"regexp"
	"strconv"
	"syscall"
	"time"

	"golang.org/x/sys/windows"
)

// FlexRadio is one radio found by discovery.
type FlexRadio struct {
	IP       string `json:"ip"`
	Port     int    `json:"port"`
	Model    string `json:"model"`
	Nickname string `json:"nickname"`
	Serial   string `json:"serial"`
	Callsign string `json:"callsign"`
}

// FlexRadios on the LAN broadcast a discovery datagram to UDP :4992 about once a
// second. DiscoverFlex listens for that broadcast for the given duration and
// returns the unique radios seen. Best effort: if the port can't be bound
// (SmartSDR running, firewall…), it returns what it has (often nothing) and the
// user falls back to entering the IP by hand.
func DiscoverFlex(timeout time.Duration) ([]FlexRadio, error) {
	if timeout <= 0 {
		timeout = 2 * time.Second
	}
	// Bind :4992 with SO_REUSEADDR so we coexist with SmartSDR, which also
	// listens for the same broadcast.
	lc := net.ListenConfig{
		Control: func(_, _ string, c syscall.RawConn) error {
			var serr error
			_ = c.Control(func(fd uintptr) {
				serr = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
			})
			return serr
		},
	}
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	pc, err := lc.ListenPacket(ctx, "udp4", ":4992")
	if err != nil {
		return nil, err
	}
	defer pc.Close()

	_ = pc.SetReadDeadline(time.Now().Add(timeout))
	found := map[string]FlexRadio{}
	buf := make([]byte, 2048)
	for {
		n, _, err := pc.ReadFrom(buf)
		if err != nil {
			break // deadline reached or socket closed
		}
		if r, ok := parseFlexDiscovery(buf[:n]); ok && r.IP != "" {
			if _, dup := found[r.IP]; !dup {
				found[r.IP] = r
			}
		}
	}
	out := make([]FlexRadio, 0, len(found))
	for _, r := range found {
		out = append(out, r)
	}
	return out, nil
}

var (
	reFlexModel    = regexp.MustCompile(`model=(\S+)`)
	reFlexIP       = regexp.MustCompile(`ip=(\S+)`)
	reFlexPort     = regexp.MustCompile(`port=(\d+)`)
	reFlexSerial   = regexp.MustCompile(`serial=(\S+)`)
	reFlexNickname = regexp.MustCompile(`nickname=(\S+)`)
	reFlexCallsign = regexp.MustCompile(`callsign=(\S+)`)
)

// parseFlexDiscovery extracts radio fields from a VITA-49 discovery datagram.
// The payload carries a space-separated key=value ASCII blob after a binary
// header, so we scan the whole packet text for the keys we need.
func parseFlexDiscovery(pkt []byte) (FlexRadio, bool) {
	s := string(pkt)
	m := reFlexIP.FindStringSubmatch(s)
	if m == nil {
		return FlexRadio{}, false
	}
	r := FlexRadio{IP: m[1], Port: 4992}
	if mm := reFlexPort.FindStringSubmatch(s); mm != nil {
		if p, err := strconv.Atoi(mm[1]); err == nil && p > 0 {
			r.Port = p
		}
	}
	if mm := reFlexModel.FindStringSubmatch(s); mm != nil {
		r.Model = mm[1]
	}
	if mm := reFlexSerial.FindStringSubmatch(s); mm != nil {
		r.Serial = mm[1]
	}
	if mm := reFlexNickname.FindStringSubmatch(s); mm != nil {
		r.Nickname = mm[1]
	}
	if mm := reFlexCallsign.FindStringSubmatch(s); mm != nil {
		r.Callsign = mm[1]
	}
	return r, true
}
