package voicegateway

import (
	"context"
	"time"

	"github.com/diamondburned/arikawa/v2/discord"
	"github.com/pkg/errors"
)

var (
	// ErrMissingForIdentify is an error when we are missing information to identify.
	ErrMissingForIdentify = errors.New("missing GuildID, UserID, SessionID, or Token for identify")

	// ErrMissingForResume is an error when we are missing information to resume.
	ErrMissingForResume = errors.New("missing GuildID, SessionID, or Token for resuming")
)

// OPCode 0
// https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-identify-payload
type IdentifyData struct {
	GuildID   discord.GuildID `json:"server_id"` // yes, this should be "server_id"
	UserID    discord.UserID  `json:"user_id"`
	SessionID string          `json:"session_id"`
	Token     string          `json:"token"`
}

// Identify sends an Identify operation (opcode 0) to the Gateway Gateway.
func (c *Gateway) Identify() error {
	ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
	defer cancel()

	return c.IdentifyCtx(ctx)
}

// IdentifyCtx sends an Identify operation (opcode 0) to the Gateway Gateway.
func (c *Gateway) IdentifyCtx(ctx context.Context) error {
	guildID := c.state.GuildID
	userID := c.state.UserID
	sessionID := c.state.SessionID
	token := c.state.Token

	if guildID == 0 || userID == 0 || sessionID == "" || token == "" {
		return ErrMissingForIdentify
	}

	return c.SendCtx(ctx, IdentifyOP, IdentifyData{
		GuildID:   guildID,
		UserID:    userID,
		SessionID: sessionID,
		Token:     token,
	})
}

// OPCode 1
// https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-udp-connection-example-select-protocol-payload
type SelectProtocol struct {
	Protocol string             `json:"protocol"`
	Data     SelectProtocolData `json:"data"`
}

type SelectProtocolData struct {
	Address string `json:"address"`
	Port    uint16 `json:"port"`
	Mode    string `json:"mode"`
}

// SelectProtocol sends a Select Protocol operation (opcode 1) to the Gateway Gateway.
func (c *Gateway) SelectProtocol(data SelectProtocol) error {
	ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
	defer cancel()

	return c.SelectProtocolCtx(ctx, data)
}

// SelectProtocolCtx sends a Select Protocol operation (opcode 1) to the Gateway Gateway.
func (c *Gateway) SelectProtocolCtx(ctx context.Context, data SelectProtocol) error {
	return c.SendCtx(ctx, SelectProtocolOP, data)
}

// OPCode 3
// https://discord.com/developers/docs/topics/voice-connections#heartbeating-example-heartbeat-payload
// type Heartbeat uint64

// Heartbeat sends a Heartbeat operation (opcode 3) to the Gateway Gateway.
func (c *Gateway) Heartbeat() error {
	ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
	defer cancel()

	return c.HeartbeatCtx(ctx)
}

// HeartbeatCtx sends a Heartbeat operation (opcode 3) to the Gateway Gateway.
func (c *Gateway) HeartbeatCtx(ctx context.Context) error {
	return c.SendCtx(ctx, HeartbeatOP, time.Now().UnixNano())
}

// https://discord.com/developers/docs/topics/voice-connections#speaking
type SpeakingFlag uint64

const (
	NotSpeaking SpeakingFlag = 0
	Microphone  SpeakingFlag = 1 << iota
	Soundshare
	Priority
)

// OPCode 5
// https://discord.com/developers/docs/topics/voice-connections#speaking-example-speaking-payload
type SpeakingData struct {
	Speaking SpeakingFlag   `json:"speaking"`
	Delay    int            `json:"delay"`
	SSRC     uint32         `json:"ssrc"`
	UserID   discord.UserID `json:"user_id,omitempty"`
}

// Speaking sends a Speaking operation (opcode 5) to the Gateway Gateway.
func (c *Gateway) Speaking(flag SpeakingFlag) error {
	ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
	defer cancel()

	return c.SpeakingCtx(ctx, flag)
}

// SpeakingCtx sends a Speaking operation (opcode 5) to the Gateway Gateway.
func (c *Gateway) SpeakingCtx(ctx context.Context, flag SpeakingFlag) error {
	// How do we allow a user to stop speaking?
	// Also: https://discordapp.com/developers/docs/topics/voice-connections#voice-data-interpolation

	return c.SendCtx(ctx, SpeakingOP, SpeakingData{
		Speaking: flag,
		Delay:    0,
		SSRC:     c.ready.SSRC,
	})
}

// OPCode 7
// https://discord.com/developers/docs/topics/voice-connections#resuming-voice-connection-example-resume-connection-payload
type ResumeData struct {
	GuildID   discord.GuildID `json:"server_id"` // yes, this should be "server_id"
	SessionID string          `json:"session_id"`
	Token     string          `json:"token"`
}

// Resume sends a Resume operation (opcode 7) to the Gateway Gateway.
func (c *Gateway) Resume() error {
	ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
	defer cancel()
	return c.ResumeCtx(ctx)
}

// ResumeCtx sends a Resume operation (opcode 7) to the Gateway Gateway.
func (c *Gateway) ResumeCtx(ctx context.Context) error {
	guildID := c.state.GuildID
	sessionID := c.state.SessionID
	token := c.state.Token

	if !guildID.IsValid() || sessionID == "" || token == "" {
		return ErrMissingForResume
	}

	return c.SendCtx(ctx, ResumeOP, ResumeData{
		GuildID:   guildID,
		SessionID: sessionID,
		Token:     token,
	})
}