package controller

import (
	"errors"
	"log"
	"strings"

	socketio "github.com/googollee/go-socket.io"
	"github.com/hyperzlib/isekai-remote-playback/context"
	"github.com/hyperzlib/isekai-remote-playback/events"
	"github.com/hyperzlib/isekai-remote-playback/store"
	"github.com/hyperzlib/isekai-remote-playback/utils"
	"github.com/pochard/commons/randstr"
)

type ClientSocketContext struct {
	Token    string
	User     string
	PairCode string
}

type ClientSocketController struct {
	SocketController
}

var clientSocketControllerInstance *ClientSocketController

func NewClientSocketController(basePath string, ctx *context.ServerContext) (*ClientSocketController, error) {
	instance := &ClientSocketController{
		SocketController{
			basePath:   basePath,
			sockets:    ctx.Sockets,
			events:     ctx.EventSource,
			context:    ctx,
			storeModel: store.NewStoreModel(),
		},
	}

	err := instance.Initialize()
	if err != nil {
		return nil, err
	}

	clientSocketControllerInstance = instance

	return instance, nil
}

func (c *ClientSocketController) Initialize() error {
	server := c.sockets

	server.OnConnect(c.basePath, func(s socketio.Conn) error {
		s.SetContext(&ClientSocketContext{})
		return nil
	})

	server.OnDisconnect(c.basePath, c.OnDisconnect)

	server.OnEvent(c.basePath, "token:refresh", c.RefreshToken)

	server.OnEvent(c.basePath, "token:set", c.SetToken)

	server.OnEvent(c.basePath, "code:get", c.GetCode)

	server.OnEvent(c.basePath, "player:name:set", c.SetPlayerName)
	server.OnEvent(c.basePath, "player:play", c.SetPlayerName)

	server.OnError(c.basePath, func(s socketio.Conn, e error) {
		log.Println("meet error:", e)
	})

	return nil
}

// SwitchRoom 切换room,并退出所有同前缀的room
func (c *ClientSocketController) SwitchRoom(socket socketio.Conn, prefix string, room string) {
	prefixWithColon := prefix + ":"
	for _, roomName := range socket.Rooms() {
		if strings.HasPrefix(roomName, prefixWithColon) {
			socket.Leave(roomName)
		}
	}
	if room != "" {
		socket.Join(prefixWithColon + ":" + room)
	}
}

// OnDisconnect 处理连接断开
func (c *ClientSocketController) OnDisconnect(socket socketio.Conn, reason string) {
	ctx := GetContext[ClientSocketContext](socket)
	// 删除存储中的code
	if ctx.PairCode != "" {
		c.storeModel.RemoveCodeInfo(ctx.PairCode)

		if ctx.Token != "" {
			playerInfo := c.storeModel.GetPlayerInfo(ctx.Token)
			if playerInfo != nil {
				playerInfo.PairCode = ""
				c.storeModel.SetPlayerInfo(ctx.Token, playerInfo)
			}
		}
	}
}

// RefreshToken 刷新token
func (c *ClientSocketController) RefreshToken(socket socketio.Conn) {
	ctx := GetContext[ClientSocketContext](socket)

	oldToken := ctx.Token
	var playerInfo *store.PlayerInfo
	if oldToken != "" {
		playerInfo = c.storeModel.GetPlayerInfo(oldToken)
	}

	newToken := randstr.RandomAlphanumeric(32)
	ctx.Token = newToken

	if playerInfo == nil {
		playerInfo = new(store.PlayerInfo)
	} else {
		c.storeModel.RemovePlayerInfo(oldToken)
	}

	if playerInfo.PairCode != "" { // 删除存储中的code
		c.storeModel.RemoveCodeInfo(playerInfo.PairCode)
		playerInfo.PairCode = ""
	}

	c.storeModel.SetPlayerInfo(newToken, playerInfo)

	socket.Emit("token:update", newToken)
}

// SetToken 设置token
func (c *ClientSocketController) SetToken(socket socketio.Conn, token string) {
	ctx := GetContext[ClientSocketContext](socket)

	if token != "" {
		ctx.Token = token
		playerInfo := c.storeModel.GetPlayerInfo(token)

		if playerInfo == nil {
			socket.Emit("token:expired")
			return
		}

		if playerInfo.BindingUser != "" {
			ctx.User = playerInfo.BindingUser
			userName := playerInfo.BindingUser
			userInfo := c.storeModel.GetUserInfo(playerInfo.BindingUser)
			if userInfo != nil {
				userName = userInfo.DisplayName
			}

			// 通知客户端
			socket.Emit("user:update", userName)
			// 加入房间
			c.SwitchRoom(socket, "user", playerInfo.BindingUser)
		}

		if playerInfo.PairCode != "" { // 删除存储中的code
			c.storeModel.RemoveCodeInfo(playerInfo.PairCode)
			playerInfo.PairCode = ""
			c.storeModel.SetPlayerInfo(ctx.Token, playerInfo)
		}

		socket.Emit("token:update", token)
	}
}

// GetCode 获取连接码
func (c *ClientSocketController) GetCode(socket socketio.Conn) {
	ctx := GetContext[ClientSocketContext](socket)

	newCode := randstr.RandomNumeric(6)
	socket.Emit("code:update", newCode)
	ctx.PairCode = newCode
}

// SetPlayerName 设置播放器设备名
func (c *ClientSocketController) SetPlayerName(socket socketio.Conn, name string) {
	ctx := GetContext[ClientSocketContext](socket)
	playerInfo := c.storeModel.GetPlayerInfo(ctx.Token)

	if playerInfo == nil {
		return
	}

	playerInfo.Name = name
	c.storeModel.SetPlayerInfo(ctx.Token, playerInfo)
	socket.Emit("player:name:update", name)
}

// PingbackPlay 播放器开始播放
func (c *ClientSocketController) PingbackPlay(socket socketio.Conn, currentTime int64, musicId string, playTime int64) {
	ctx := GetContext[ClientSocketContext](socket)
	c.events.EmitEvent("playerStartPlay", &events.PlayerStartPlay{
		User:        ctx.User,
		MusicId:     musicId,
		CurrentTime: currentTime,
		PlayTime:    playTime,
	})
}

// PingbackPause 播放器暂停
func (c *ClientSocketController) PingbackPause(socket socketio.Conn, currentTime int64, musicId string, playTime int64) {
	ctx := GetContext[ClientSocketContext](socket)
	c.events.EmitEvent("playerPause", &events.PlayerPause{
		User:        ctx.User,
		MusicId:     musicId,
		CurrentTime: currentTime,
		PlayTime:    playTime,
	})
}

// PingbackStop 播放器停止
func (c *ClientSocketController) PingbackStop(socket socketio.Conn, musicId string, isEnd bool) {
	ctx := GetContext[ClientSocketContext](socket)
	c.events.EmitEvent("playerStop", &events.PlayerStop{
		User:    ctx.User,
		MusicId: musicId,
	})
}

// BindPlayer 用户触发绑定播放器
func (c *ClientSocketController) BindPlayer(user string, code string) (*store.PlayerInfo, error) {
	// code为空则不绑定
	if code == "" {
		return nil, errors.New("code invalid")
	}

	c.sockets.ForEach("/", "", func(socket socketio.Conn) {
		ctx := GetContext[ClientSocketContext](socket)
		if ctx.PairCode == code {
			// 找到需要绑定的客户端
			c.events.EmitEvent("bindUser", &events.BindUserEvent{
				Token: ctx.Token,
				User:  user,
			})

			userName := user
			userInfo := c.storeModel.GetUserInfo(user)
			if userInfo != nil {
				userName = userInfo.DisplayName
			}

			// 通知客户端
			socket.Emit("user:update", userName)

			// 加入房间
			c.SwitchRoom(socket, "user", user)
			return
		}
	})
	return nil, errors.New("code not exists")
}

// OnPlayMusic 用户触发播放音乐
func (c *ClientSocketController) OnPlayMusic(eventObj ...interface{}) {
	event, err := utils.GetEvent[events.PlayMusicEvent](eventObj)
	if err != nil {
		log.Panicln("Cannot get event in OnPlayMusic: ", err.Error())
	}

	type PlayMusicResponse struct {
		MusicId  string            `json:"music_id"`
		Src      map[string]string `json:"src"`
		Name     string            `json:"name"`
		LyricUrl string            `json:"lyric_url"`
		ImageUrl string            `json:"image_url"`
		Loop     bool              `json:"loop"`
		Pingback bool              `json:"pingback"`
	}
	res := &PlayMusicResponse{
		Src:      event.Src,
		Name:     event.Name,
		LyricUrl: event.LyricUrl,
		ImageUrl: event.ImageUrl,
		Loop:     event.Loop,
		Pingback: event.Pingback,
	}

	c.sockets.BroadcastToRoom("/", "user"+event.User, "music:play", res)
}

func (c *ClientSocketController) OnPlaySE(eventObj interface{}) {

}