package game

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

	"hk4e/gs/dao"
	"hk4e/gs/model"
	"hk4e/pkg/logger"
	"hk4e/protocol/proto"
)

type UserManager struct {
	dao            *dao.Dao
	playerMap      map[uint32]*model.Player
	playerMapLock  sync.RWMutex
	localEventChan chan *LocalEvent
}

func NewUserManager(dao *dao.Dao, localEventChan chan *LocalEvent) (r *UserManager) {
	r = new(UserManager)
	r.dao = dao
	r.playerMap = make(map[uint32]*model.Player)
	r.localEventChan = localEventChan
	return r
}

func (u *UserManager) GetUserOnlineState(userId uint32) bool {
	u.playerMapLock.RLock()
	player, exist := u.playerMap[userId]
	u.playerMapLock.RUnlock()
	if !exist {
		return false
	} else {
		return player.Online
	}
}

func (u *UserManager) GetOnlineUser(userId uint32) *model.Player {
	u.playerMapLock.RLock()
	player, exist := u.playerMap[userId]
	u.playerMapLock.RUnlock()
	if !exist {
		return nil
	} else {
		if player.Online {
			return player
		} else {
			return nil
		}
	}
}

func (u *UserManager) GetAllOnlineUserList() map[uint32]*model.Player {
	onlinePlayerMap := make(map[uint32]*model.Player)
	u.playerMapLock.RLock()
	for userId, player := range u.playerMap {
		if player.Online == false {
			continue
		}
		onlinePlayerMap[userId] = player
	}
	u.playerMapLock.RUnlock()
	return onlinePlayerMap
}

type PlayerRegInfo struct {
	Exist     bool
	Req       *proto.SetPlayerBornDataReq
	UserId    uint32
	ClientSeq uint32
}

func (u *UserManager) CheckUserExistOnReg(userId uint32, req *proto.SetPlayerBornDataReq, clientSeq uint32) (exist bool, asyncWait bool) {
	u.playerMapLock.RLock()
	_, exist = u.playerMap[userId]
	u.playerMapLock.RUnlock()
	if exist {
		return true, false
	} else {
		go func() {
			player := u.loadUserFromDb(userId)
			exist = false
			if player != nil {
				exist = true
			}
			u.localEventChan <- &LocalEvent{
				EventId: CheckUserExistOnRegFromDbFinish,
				Msg: &PlayerRegInfo{
					Exist:     exist,
					Req:       req,
					UserId:    userId,
					ClientSeq: clientSeq,
				},
			}
		}()
		return false, true
	}
}

func (u *UserManager) LoadTempOfflineUserSync(userId uint32) *model.Player {
	u.playerMapLock.RLock()
	player, exist := u.playerMap[userId]
	u.playerMapLock.RUnlock()
	if exist {
		return player
	} else {
		player = u.loadUserFromDb(userId)
		if player == nil {
			return nil
		}
		player.DbState = model.DbOffline
		u.playerMapLock.Lock()
		u.playerMap[player.PlayerID] = player
		u.playerMapLock.Unlock()
		return player
	}
}

func (u *UserManager) loadUserFromDb(userId uint32) *model.Player {
	player, err := u.dao.QueryPlayerByID(userId)
	if err != nil {
		logger.LOG.Error("query player error: %v", err)
		return nil
	}
	return player
}

func (u *UserManager) AddUser(player *model.Player) {
	if player == nil {
		return
	}
	u.ChangeUserDbState(player, model.DbInsert)
	u.playerMapLock.Lock()
	u.playerMap[player.PlayerID] = player
	u.playerMapLock.Unlock()
}

func (u *UserManager) DeleteUser(player *model.Player) {
	if player == nil {
		return
	}
	u.ChangeUserDbState(player, model.DbDelete)
	u.playerMapLock.Lock()
	u.playerMap[player.PlayerID] = player
	u.playerMapLock.Unlock()
}

func (u *UserManager) UpdateUser(player *model.Player) {
	if player == nil {
		return
	}
	u.ChangeUserDbState(player, model.DbUpdate)
	u.playerMapLock.Lock()
	u.playerMap[player.PlayerID] = player
	u.playerMapLock.Unlock()
}

type PlayerLoginInfo struct {
	UserId    uint32
	Player    *model.Player
	ClientSeq uint32
}

func (u *UserManager) OnlineUser(userId uint32, clientSeq uint32) (*model.Player, bool) {
	u.playerMapLock.RLock()
	player, exist := u.playerMap[userId]
	u.playerMapLock.RUnlock()
	if exist {
		u.ChangeUserDbState(player, model.DbNormal)
		return player, false
	} else {
		go func() {
			player = u.loadUserFromDb(userId)
			if player != nil {
				player.DbState = model.DbNormal
				u.playerMapLock.Lock()
				u.playerMap[player.PlayerID] = player
				u.playerMapLock.Unlock()
			}
			u.localEventChan <- &LocalEvent{
				EventId: LoadLoginUserFromDbFinish,
				Msg: &PlayerLoginInfo{
					UserId:    userId,
					Player:    player,
					ClientSeq: clientSeq,
				},
			}
		}()
		return nil, true
	}
}

func (u *UserManager) OfflineUser(player *model.Player) {
	if player == nil {
		return
	}
	u.ChangeUserDbState(player, model.DbOffline)
	u.playerMapLock.Lock()
	u.playerMap[player.PlayerID] = player
	u.playerMapLock.Unlock()
}

func (u *UserManager) ChangeUserDbState(player *model.Player, state int) {
	if player == nil {
		return
	}
	switch player.DbState {
	case model.DbInsert:
		if state == model.DbDelete {
			player.DbState = model.DbDelete
		}
	case model.DbDelete:
	case model.DbUpdate:
		if state == model.DbDelete {
			player.DbState = model.DbDelete
		} else if state == model.DbOffline {
			player.DbState = model.DbOffline
		}
	case model.DbNormal:
		if state == model.DbDelete {
			player.DbState = model.DbDelete
		} else if state == model.DbUpdate {
			player.DbState = model.DbUpdate
		} else if state == model.DbOffline {
			player.DbState = model.DbOffline
		}
	case model.DbOffline:
		if state == model.DbDelete {
			player.DbState = model.DbDelete
		} else if state == model.DbUpdate {
			player.DbState = model.DbUpdate
		} else if state == model.DbNormal {
			player.DbState = model.DbNormal
		}
	}
}

func (u *UserManager) StartAutoSaveUser() {
	// 用户数据库定时同步协程
	go func() {
		ticker := time.NewTicker(time.Minute * 5)
		for {
			u.SaveUser()
			<-ticker.C
		}
	}()
}

func (u *UserManager) SaveUser() {
	logger.LOG.Info("auto save user start")
	playerMapTemp := make(map[uint32]*model.Player)
	u.playerMapLock.RLock()
	for k, v := range u.playerMap {
		playerMapTemp[k] = v
	}
	u.playerMapLock.RUnlock()
	logger.LOG.Info("copy user map finish")
	insertList := make([]*model.Player, 0)
	deleteList := make([]uint32, 0)
	updateList := make([]*model.Player, 0)
	for k, v := range playerMapTemp {
		switch v.DbState {
		case model.DbInsert:
			insertList = append(insertList, v)
			playerMapTemp[k].DbState = model.DbNormal
		case model.DbDelete:
			deleteList = append(deleteList, v.PlayerID)
			delete(playerMapTemp, k)
		case model.DbUpdate:
			updateList = append(updateList, v)
			playerMapTemp[k].DbState = model.DbNormal
		case model.DbNormal:
			continue
		case model.DbOffline:
			updateList = append(updateList, v)
			delete(playerMapTemp, k)
		}
	}
	insertListJson, err := json.Marshal(insertList)
	logger.LOG.Debug("insertList: %v", string(insertListJson))
	deleteListJson, err := json.Marshal(deleteList)
	logger.LOG.Debug("deleteList: %v", string(deleteListJson))
	updateListJson, err := json.Marshal(updateList)
	logger.LOG.Debug("updateList: %v", string(updateListJson))
	logger.LOG.Info("db state init finish")
	err = u.dao.InsertPlayerList(insertList)
	if err != nil {
		logger.LOG.Error("insert player list error: %v", err)
	}
	err = u.dao.DeletePlayerList(deleteList)
	if err != nil {
		logger.LOG.Error("delete player error: %v", err)
	}
	err = u.dao.UpdatePlayerList(updateList)
	if err != nil {
		logger.LOG.Error("update player error: %v", err)
	}
	logger.LOG.Info("db write finish")
	u.playerMapLock.Lock()
	u.playerMap = playerMapTemp
	u.playerMapLock.Unlock()
	logger.LOG.Info("auto save user finish")
}
