package kvm

import (
	"encoding/json"
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/pion/rtp"
)

var (
	audioListener *net.UDPConn
	currentPort   int
	mutex         sync.Mutex
	portList      = []int{3333}
	portIndex     = 0
)

const (
	maxAudioFrameSize = 1500
	frameDurationMs   = 20
	timestampRate     = 48000
	timestampStep     = timestampRate * frameDurationMs / 1000
)

func waitUDPPortReleased(port int, retries int, delay time.Duration) error {
	addr := fmt.Sprintf("127.0.0.1:%d", port)
	for i := 0; i < retries; i++ {
		conn, err := net.ListenPacket("udp", addr)
		if err == nil {
			conn.Close()
			return nil
		}
		time.Sleep(delay)
	}
	return fmt.Errorf("port %d still in use after %d retries", port, retries)
}

func getNextAvailablePort() int {
	for i := 0; i < len(portList); i++ {
		port := portList[portIndex]
		portIndex = (portIndex + 1) % len(portList)
		addr := fmt.Sprintf("127.0.0.1:%d", port)
		conn, err := net.ListenPacket("udp", addr)
		if err == nil {
			conn.Close()
			return port
		}
	}
	return 0
}

func StartNtpAudioServer(handleClient func(net.Conn)) {
	mutex.Lock()
	defer mutex.Unlock()

	if audioListener != nil || lastAudioState.Ready {
		StopNtpAudioServer()
	}

	port := getNextAvailablePort()
	if port == 0 {
		audioLogger.Error().Msg("no available ports to start audio server")
		return
	}

	listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: port})
	if err != nil {
		audioLogger.Error().Err(err).Msg("failed to start server on port %d")
		return
	}

	audioListener = listener
	currentPort = port

	if config.AudioMode == "usb" {
		_, err := CallAudioCtrlAction("set_audio_mode", map[string]interface{}{"audio_mode": "usb", "rtp_port": port})
		if err != nil {
			audioLogger.Error().Err(err).Msg("failed to set audio mode")
		}
		_, err = CallAudioCtrlAction("set_audio_enable", map[string]interface{}{"audio_enable": true})
		if err != nil {
			audioLogger.Error().Err(err).Msg("failed to set audio enable")
		}
	} else {
		_, err := CallAudioCtrlAction("set_audio_mode", map[string]interface{}{"audio_mode": "hdmi", "rtp_port": port})
		if err != nil {
			audioLogger.Error().Err(err).Msg("failed to set audio mode")
		}
		_, err = CallAudioCtrlAction("set_audio_enable", map[string]interface{}{"audio_enable": true})
		if err != nil {
			audioLogger.Error().Err(err).Msg("failed to set audio enable")
		}
	}

	go handleClient(listener)
}

func StopNtpAudioServer() {
	_, err := CallAudioCtrlAction("set_audio_enable", map[string]interface{}{"audio_enable": false})
	if err != nil {
		audioLogger.Error().Err(err).Msg("failed to set audio enable")
	}

	if audioListener != nil {
		audioListener.Close()
		audioListener = nil
	}

	if currentPort != 0 {
		if err := waitUDPPortReleased(currentPort, 10, 200*time.Millisecond); err != nil {
			audioLogger.Error().Err(err).Msg("port not released")
		}
		currentPort = 0
	}

	audioLogger.Info().Msg("audio server stopped")
}

func handleAudioClient(conn net.Conn) {
	defer conn.Close()

	audioLogger.Info().Msg("native audio socket client connected")
	inboundPacket := make([]byte, maxAudioFrameSize)
	var timestamp uint32
	var packet rtp.Packet

	for {
		n, err := conn.Read(inboundPacket)
		if err != nil {
			audioLogger.Warn().Err(err).Msg("error during read")
			return
		}

		if currentSession != nil {
			if err := packet.Unmarshal(inboundPacket[:n]); err != nil {
				audioLogger.Warn().Err(err).Msg("error unmarshalling audio socket packet")
				continue
			}

			timestamp += timestampStep
			packet.Timestamp = timestamp
			buf, err := packet.Marshal()
			if err != nil {
				audioLogger.Warn().Err(err).Msg("error marshalling packet")
				continue
			}

			if _, err := currentSession.AudioTrack.Write(buf); err != nil {
				audioLogger.Warn().Err(err).Msg("error writing sample")
			}
		}
	}
}

type AudioInputState struct {
	Ready bool   `json:"ready"`
	Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range
}

var lastAudioState AudioInputState

func HandleAudioStateMessage(event CtrlResponse) {
	audioState := AudioInputState{}
	err := json.Unmarshal(event.Data, &audioState)
	if err != nil {
		audioLogger.Warn().Err(err).Msg("Error parsing audio state json")
		return
	}
	lastAudioState = audioState
}
