package aoiprxkit

import (
	"context"
	"fmt"
	"net"
	"sync"
	"time"
)

type SAPListenerConfig struct {
	Group         string
	Port          int
	InterfaceName string
	ReadBuffer    int
}

func DefaultSAPListenerConfig() SAPListenerConfig {
	return SAPListenerConfig{
		Group:      DefaultSAPGroup,
		Port:       DefaultSAPPort,
		ReadBuffer: 1 << 20,
	}
}

type SAPHandler func(announcement SAPAnnouncement)

type SAPListener struct {
	cfg      SAPListenerConfig
	onPacket SAPHandler

	mu       sync.Mutex
	conn     *net.UDPConn
	cancel   context.CancelFunc
	done     chan struct{}
	doneOnce sync.Once
}

func NewSAPListener(cfg SAPListenerConfig, onPacket SAPHandler) (*SAPListener, error) {
	if cfg.Group == "" {
		cfg.Group = DefaultSAPGroup
	}
	if cfg.Port == 0 {
		cfg.Port = DefaultSAPPort
	}
	if onPacket == nil {
		return nil, fmt.Errorf("onPacket must not be nil")
	}
	if net.ParseIP(cfg.Group) == nil {
		return nil, fmt.Errorf("invalid SAP group: %q", cfg.Group)
	}
	return &SAPListener{
		cfg:      cfg,
		onPacket: onPacket,
		done:     make(chan struct{}),
	}, nil
}

func (l *SAPListener) Start(ctx context.Context) error {
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.conn != nil {
		return fmt.Errorf("SAP listener already started")
	}

	ifi, err := resolveMulticastInterface(l.cfg.InterfaceName)
	if err != nil {
		return err
	}
	group := net.ParseIP(l.cfg.Group)
	addr := &net.UDPAddr{IP: group, Port: l.cfg.Port}
	conn, err := net.ListenMulticastUDP("udp4", ifi, addr)
	if err != nil {
		return fmt.Errorf("listen SAP multicast UDP: %w", err)
	}
	if l.cfg.ReadBuffer > 0 {
		_ = conn.SetReadBuffer(l.cfg.ReadBuffer)
	}

	cctx, cancel := context.WithCancel(ctx)
	l.conn = conn
	l.cancel = cancel
	l.done = make(chan struct{})
	l.doneOnce = sync.Once{}
	go l.loop(cctx)
	return nil
}

func (l *SAPListener) Stop() error {
	l.mu.Lock()
	if l.conn == nil {
		l.mu.Unlock()
		return nil
	}
	conn := l.conn
	cancel := l.cancel
	done := l.done
	l.conn = nil
	l.cancel = nil
	l.mu.Unlock()

	if cancel != nil {
		cancel()
	}
	_ = conn.Close()
	<-done
	return nil
}

func (l *SAPListener) loop(ctx context.Context) {
	defer l.doneOnce.Do(func() { close(l.done) })

	buf := make([]byte, 64*1024)
	for {
		select {
		case <-ctx.Done():
			return
		default:
		}
		_ = l.conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
		n, src, err := l.conn.ReadFromUDP(buf)
		if err != nil {
			if ne, ok := err.(net.Error); ok && ne.Timeout() {
				continue
			}
			return
		}
		pkt, err := ParseSAPPacket(buf[:n])
		if err != nil {
			continue
		}
		if pkt.PayloadType != "application/sdp" {
			continue
		}
		sdp := string(pkt.Payload)
		info, err := ParseMinimalSDP(sdp)
		if err != nil {
			continue
		}
		l.onPacket(SAPAnnouncement{
			ReceivedAt:  time.Now().UTC().Format(time.RFC3339Nano),
			SourceAddr:  src.String(),
			MessageID:   pkt.MessageIDHash,
			Delete:      pkt.IsDelete,
			PayloadType: pkt.PayloadType,
			SDP:         sdp,
			ParsedSDP:   info,
		})
	}
}
