package client

import (
	"fmt"
	"math/rand"
	"net"
	"runtime"
	"time"

	"github.com/adamweixuan/getty"
	gxsync "github.com/adamweixuan/gostnops/sync"

	"virjar.com/majora-go/common"
	"virjar.com/majora-go/infra"
	"virjar.com/majora-go/log"
)

var (
	taskPool = gxsync.NewTaskPoolSimple(runtime.GOMAXPROCS(-1) * 100)
)

func (client *Client) connect() {
	hostPort := client.config.TunnelAddr
	if len(hostPort) == 0 {
		panic("invalid nat host/port info")
	}

	reConnect := client.config.ReconnInterval

	gettyCli := getty.NewTCPClient(
		getty.WithServerAddress(hostPort),
		getty.WithConnectionNumber(1),
		getty.WithClientTaskPool(taskPool),
		getty.WithReconnectInterval(int(reConnect.Milliseconds())),
		getty.WithLocalAddressClient(client.localAddr))
	gettyCli.RunEventLoop(NewClientSession(client))
	client.natTunnel = gettyCli
}

func NewClientSession(client *Client) func(getty.Session) error {
	return func(session getty.Session) error {
		return InitialSession(session, client)
	}
}

func InitialSession(session getty.Session, client *Client) (err error) {
	tcpConn, ok := session.Conn().(*net.TCPConn)
	if !ok {
		panic(fmt.Sprintf("invalid session %+v", session.Conn()))
	}

	if err = tcpConn.SetNoDelay(true); err != nil {
		return err
	}
	if err = tcpConn.SetKeepAlive(true); err != nil {
		return err
	}
	if err = tcpConn.SetKeepAlivePeriod(common.KeepAliveTimeout); err != nil {
		return err
	}
	if err = tcpConn.SetReadBuffer(common.MB); err != nil {
		return err
	}
	if err = tcpConn.SetWriteBuffer(common.MB); err != nil {
		return err
	}

	session.SetName(common.SessionName)
	session.SetMaxMsgLen(common.KB8)
	session.SetReadTimeout(common.ReadTimeout)
	session.SetWriteTimeout(common.WriteTimeout)
	session.SetWaitTime(common.WaitTimeout)

	if client.config.Redial.Valid() {
		rand.Seed(time.Now().UnixNano())
		// 加上随机 防止vps在同时间重启
		randDuration := rand.Int63n(time.Minute.Milliseconds() * 5)
		interval := randDuration + client.config.Redial.RedialDuration.Milliseconds()
		log.Run().Infof("redial interval %+v", time.Duration(interval)*time.Millisecond)
		session.SetCronPeriod(int(interval))
	}
	session.SetPkgHandler(PkgCodec)
	session.SetEventListener(&MajoraEventListener{
		client: client,
	})
	return nil
}

func (client *Client) Redial(session getty.Session) {
	log.Run().Infof("redial start ...%v", client.config.Redial.Valid())
	if !client.config.Redial.Valid() {
		return
	}
	log.Run().Warn("redial send offline message ...")
	if _, _, err := session.WritePkg(OfflinePacket, 0); err != nil {
		log.Run().Warnf("write offline to server error %s", err.Error())
	}
	log.Run().Warn("redial close local session")
	client.CloseAll(session)
	time.Sleep(client.config.Redial.WaitTime)
	infra.Redial(client.config, session)
}
