package gameService

import (
	"fmt"
	"game/common/config"
	"game/common/constant"
	"game/common/proto/pb"
	"game/common/userBindService"
	"game/common/utils"
	"github.com/fox/fox/db"
	"github.com/fox/fox/etcd"
	"github.com/fox/fox/ipb"
	"github.com/fox/fox/log"
	"github.com/fox/fox/processor"
	"github.com/fox/fox/service"
	"github.com/go-redis/redis/v8"
	"github.com/golang/protobuf/proto"
	"time"
)

type GameService struct {
	*service.NatsService
	processor   *processor.Processor
	bindService *userBindService.UserBindService
}

func NewGameService(param *service.InitNatsServiceParams, cfgRedis *config.Redis) *GameService {
	var err error
	s := new(GameService)
	s.NatsService, err = service.NewNatsService(param)
	if err != nil {
		log.Fatal(err.Error())
		return nil
	}
	var userBindServiceRedis *redis.Client
	userBindServiceRedis, err = db.InitRedis(cfgRedis.Password, cfgRedis.Host, cfgRedis.Port, constant.Redis3UserBindService)
	if err != nil {
		log.Fatal(err.Error())
		return nil
	}
	utils.AutoSetRedisPool(userBindServiceRedis)
	s.bindService = userBindService.NewUserBindService(userBindServiceRedis, s.ServiceEtcd())
	s.processor = processor.NewProcessor()
	return s
}

func (s *GameService) BindService() *userBindService.UserBindService {
	return s.bindService
}

func (s *GameService) Processor() *processor.Processor {
	return s.processor
}

// 依赖子类实现，通过节点id获取topic名再调用SendByTopic
func (s *GameService) SendByServiceId(serviceId int, msg *ipb.InternalMsg) error {
	node, err := s.findNode(pb.ServiceTypeId(serviceId), msg.UserId)
	if err != nil {
		return err
	}
	topic := service.TopicEx(node.Name)
	return s.SendByTopic(topic, msg)
}

// 向内部服务发送消息
func (s *GameService) SendServiceData(topic string, connId uint32, userId int64, msgId int32, data []byte) {
	iMsg := ipb.MakeMsg(s.Name(), connId, userId, msgId, data)
	_ = s.SendByTopic(topic, iMsg)
}

// 向内部服务发送消息
func (s *GameService) SendServiceMsg(sid pb.ServiceTypeId, userId int64, msgId int32, msg proto.Message) {
	node, err := s.findNode(sid, userId)
	if err != nil {
		log.Error(err.Error())
		return
	}
	topic := service.TopicEx(node.Name)
	s.SendServiceMsgByTopic(topic, 0, userId, msgId, msg)
}

func (s *GameService) SendServiceMsgByTopic(topic string, connId uint32, userId int64, msgId int32, msg proto.Message) {
	log.DebugF("send to:%v msg id:%v, msg:%v", topic, pb.MsgId(msgId), msg.String())
	data, _ := proto.Marshal(msg)
	s.SendServiceData(topic, connId, userId, msgId, data)
}

// 向内部服务发送消息
func (s *GameService) findNode(serviceId pb.ServiceTypeId, uid int64) (*etcd.ServiceNode, error) {
	var err error
	var node *etcd.ServiceNode
	switch s.bindService.CheckServiceNodeStateType(serviceId) {
	case etcd.SNST_Stateful:
		node, err = s.bindService.FindServiceNode(serviceId, uid)
	case etcd.SNST_Stateless:
		node, err = s.bindService.HashServiceNode(serviceId, uid)
	case etcd.SNST_Ordered:
		node, err = s.bindService.HashServiceNode(serviceId, uid)
	default:
		log.ErrorF(s.Log("查找节点类型失败。sid:%v", serviceId))
		return nil, fmt.Errorf(s.Log("查找节点类型失败。sid:%v", serviceId))
	}
	if err != nil {
		return nil, fmt.Errorf(s.Log("查找节点类型失败。sid:%v err:%v", serviceId, err))
	}
	return node, nil
}

// call实现
func (s *GameService) CallByServiceId(serviceId int, timeout time.Duration, msg *ipb.InternalMsg) (*ipb.InternalMsg, error) {
	node, err := s.findNode(pb.ServiceTypeId(serviceId), msg.UserId)
	if err != nil {
		return nil, err
	}
	topic := service.RpcTopicEx(node.Name)
	return s.CallByTopic(topic, timeout, msg)
}
