// +build integration

package voice

import (
	"context"
	"encoding/binary"
	"io"
	"log"
	"os"
	"runtime"
	"strconv"
	"testing"
	"time"

	"github.com/diamondburned/arikawa/discord"
	"github.com/diamondburned/arikawa/gateway"
	"github.com/diamondburned/arikawa/utils/wsutil"
	"github.com/diamondburned/arikawa/voice/voicegateway"
)

func TestIntegration(t *testing.T) {
	config := mustConfig(t)

	wsutil.WSDebug = func(v ...interface{}) {
		_, file, line, _ := runtime.Caller(1)
		caller := file + ":" + strconv.Itoa(line)
		log.Println(append([]interface{}{caller}, v...)...)
	}

	v, err := NewVoiceFromToken("Bot " + config.BotToken)
	if err != nil {
		t.Fatal("Failed to create a new voice session:", err)
	}
	v.Gateway.AddIntent(gateway.IntentGuildVoiceStates)

	v.ErrorLog = func(err error) {
		t.Error(err)
	}

	if err := v.Open(); err != nil {
		t.Fatal("Failed to connect:", err)
	}
	defer v.Close()

	// Validate the given voice channel.
	c, err := v.Channel(config.VoiceChID)
	if err != nil {
		t.Fatal("Failed to get channel:", err)
	}
	if c.Type != discord.GuildVoice {
		t.Fatal("Channel isn't a guild voice channel.")
	}

	log.Println("The voice channel's name is", c.Name)

	// Grab a timer to benchmark things.
	finish := timer()

	// Join the voice channel.
	vs, err := v.JoinChannel(c.GuildID, c.ID, false, false)
	if err != nil {
		t.Fatal("Failed to join channel:", err)
	}
	defer func() {
		log.Println("Disconnecting from the voice channel.")
		if err := vs.Disconnect(); err != nil {
			t.Fatal("Failed to disconnect:", err)
		}
	}()

	finish("joining the voice channel")

	// Trigger speaking.
	if err := vs.Speaking(voicegateway.Microphone); err != nil {
		t.Fatal("Failed to start speaking:", err)
	}
	defer func() {
		log.Println("Stopping speaking.") // sounds grammatically wrong
		if err := vs.StopSpeaking(); err != nil {
			t.Fatal("Failed to stop speaking:", err)
		}
	}()

	finish("sending the speaking command")

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := vs.UseContext(ctx); err != nil {
		t.Fatal("failed to set ctx into vs:", err)
	}

	// Copy the audio?
	nicoReadTo(t, vs)

	finish("copying the audio")
}

type testConfig struct {
	BotToken  string
	VoiceChID discord.ChannelID
}

func mustConfig(t *testing.T) testConfig {
	var token = os.Getenv("BOT_TOKEN")
	if token == "" {
		t.Fatal("Missing $BOT_TOKEN")
	}

	var sid = os.Getenv("VOICE_ID")
	if sid == "" {
		t.Fatal("Missing $VOICE_ID")
	}

	id, err := discord.ParseSnowflake(sid)
	if err != nil {
		t.Fatal("Invalid $VOICE_ID:", err)
	}

	return testConfig{
		BotToken:  token,
		VoiceChID: discord.ChannelID(id),
	}
}

// file is only a few bytes lolmao
func nicoReadTo(t *testing.T, dst io.Writer) {
	t.Helper()

	f, err := os.Open("testdata/nico.dca")
	if err != nil {
		t.Fatal("Failed to open nico.dca:", err)
	}
	defer f.Close()

	var lenbuf [4]byte

	for {
		if _, err := io.ReadFull(f, lenbuf[:]); err != nil {
			if err == io.EOF {
				break
			}
			t.Fatal("failed to read:", err)
		}

		// Read the integer
		framelen := int64(binary.LittleEndian.Uint32(lenbuf[:]))

		// Copy the frame.
		if _, err := io.CopyN(dst, f, framelen); err != nil && err != io.EOF {
			t.Fatal("failed to write:", err)
		}
	}
}

// simple shitty benchmark thing
func timer() func(finished string) {
	var then = time.Now()

	return func(finished string) {
		now := time.Now()
		log.Println("Finished", finished+", took", now.Sub(then))
		then = now
	}
}