package handler_test

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/gorilla/websocket"
	"github.com/qy/hw-ws-service/internal/audio"
	"github.com/qy/hw-ws-service/internal/connection"
	"github.com/qy/hw-ws-service/internal/handler"
)

// makeWSPair creates a real WebSocket pair for testing.
// svrWS is the server side (used by our Connection), cliWS simulates the hardware.
func makeWSPair(t *testing.T) (svrWS *websocket.Conn, cliWS *websocket.Conn, cleanup func()) {
	t.Helper()

	ch := make(chan *websocket.Conn, 1)
	done := make(chan struct{})

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		up := websocket.Upgrader{CheckOrigin: func(*http.Request) bool { return true }}
		c, err := up.Upgrade(w, r, nil)
		if err != nil {
			t.Logf("upgrade error: %v", err)
			return
		}
		ch <- c
		<-done
	}))

	wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
	cli, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
	if err != nil {
		close(done)
		srv.Close()
		t.Fatalf("dial error: %v", err)
	}

	svr := <-ch
	return svr, cli, func() {
		close(done)
		svr.Close()
		cli.Close()
		srv.Close()
	}
}

// makeFrames creates n fake Opus frames of 4 bytes each.
func makeFrames(n int) [][]byte {
	frames := make([][]byte, n)
	for i := range frames {
		frames[i] = []byte{byte(i), byte(i >> 8), 0x00, 0xff}
	}
	return frames
}

// TestSendOpusStream_Empty verifies that an empty frame list returns immediately.
func TestSendOpusStream_Empty(t *testing.T) {
	svrWS, _, cleanup := makeWSPair(t)
	defer cleanup()

	conn := connection.New(svrWS, "dev1", "cli1")
	abort := make(chan struct{})

	done := make(chan struct{})
	go func() {
		handler.SendOpusStream(conn, nil, abort)
		close(done)
	}()

	select {
	case <-done:
	case <-time.After(500 * time.Millisecond):
		t.Fatal("SendOpusStream did not return immediately for empty frames")
	}
}

// TestSendOpusStream_AllFramesSent verifies that all frames reach the client.
// Uses PreBufferCount+2 frames so both pre-buffer and timed paths are exercised.
func TestSendOpusStream_AllFramesSent(t *testing.T) {
	svrWS, cliWS, cleanup := makeWSPair(t)
	defer cleanup()

	conn := connection.New(svrWS, "dev1", "cli1")

	totalFrames := audio.PreBufferCount + 2 // 3 pre-buffer + 2 timed
	frames := makeFrames(totalFrames)

	abort := make(chan struct{})
	senderDone := make(chan struct{})
	go func() {
		handler.SendOpusStream(conn, frames, abort)
		close(senderDone)
	}()

	// Read all frames from the client side (simulates hardware receiving)
	received := 0
	cliWS.SetReadDeadline(time.Now().Add(10 * time.Second))
	for received < totalFrames {
		msgType, _, err := cliWS.ReadMessage()
		if err != nil {
			t.Fatalf("client read error after %d frames: %v", received, err)
		}
		if msgType == websocket.BinaryMessage {
			received++
		}
	}

	select {
	case <-senderDone:
	case <-time.After(2 * time.Second):
		t.Fatal("SendOpusStream did not finish after all frames were sent")
	}

	if received != totalFrames {
		t.Errorf("received %d frames, want %d", received, totalFrames)
	}
}

// TestSendOpusStream_Abort verifies that closing abortCh stops streaming early.
func TestSendOpusStream_Abort(t *testing.T) {
	svrWS, _, cleanup := makeWSPair(t)
	defer cleanup()

	conn := connection.New(svrWS, "dev1", "cli1")

	// Many frames so timing control is active (pre-buffer finishes quickly,
	// then the time.After select can receive the abort signal)
	frames := makeFrames(100)

	abort := make(chan struct{})
	senderDone := make(chan struct{})
	go func() {
		handler.SendOpusStream(conn, frames, abort)
		close(senderDone)
	}()

	// Close abort after pre-buffer has had time to finish but before timed frames complete
	time.Sleep(20 * time.Millisecond)
	close(abort)

	select {
	case <-senderDone:
		// SendOpusStream returned early — correct behaviour
	case <-time.After(2 * time.Second):
		t.Fatal("SendOpusStream did not abort within 2s after closing abortCh")
	}
}

// TestSendOpusStream_PreBufferOnly verifies frames <= PreBufferCount are all sent
// without entering the timed loop (should finish nearly instantly).
func TestSendOpusStream_PreBufferOnly(t *testing.T) {
	svrWS, cliWS, cleanup := makeWSPair(t)
	defer cleanup()

	conn := connection.New(svrWS, "dev1", "cli1")

	frames := makeFrames(audio.PreBufferCount) // exactly the pre-buffer count
	abort := make(chan struct{})

	start := time.Now()
	senderDone := make(chan struct{})
	go func() {
		handler.SendOpusStream(conn, frames, abort)
		close(senderDone)
	}()

	received := 0
	cliWS.SetReadDeadline(time.Now().Add(3 * time.Second))
	for received < len(frames) {
		msgType, _, err := cliWS.ReadMessage()
		if err != nil {
			t.Fatalf("read error: %v", err)
		}
		if msgType == websocket.BinaryMessage {
			received++
		}
	}

	select {
	case <-senderDone:
	case <-time.After(time.Second):
		t.Fatal("sender did not finish")
	}

	elapsed := time.Since(start)
	// Pre-buffer frames should not wait on the timer; allow 200ms for overhead
	if elapsed > 200*time.Millisecond {
		t.Errorf("pre-buffer-only send took too long: %v (want < 200ms)", elapsed)
	}
}
