package client

import (
	"net"
	"strings"
	"sync"
	"time"

	"github.com/adamweixuan/getty"
	"virjar.com/majora-go/common"
	"virjar.com/majora-go/log"
	"virjar.com/majora-go/protocol"
)

var (
	HeartbeatPacket = protocol.TypeHeartbeat.CreatePacket()
	OfflinePacket   = protocol.TypeOffline.CreatePacket()
)

func (client *Client) handleHeartbeat(session getty.Session) {
	go func() {
		if _, _, err := session.WritePkg(HeartbeatPacket, common.HeartBeatTimeout); err != nil {
			log.Heart().Errorf("handleHeartbeat error %+v %v", err, session.IsClosed())
		} else {
			log.Heart().Infof("handleHeartbeat success")
		}
	}()
}

func (client *Client) handleConnect(packet *protocol.MajoraPacket, session getty.Session) {
	go func(packet *protocol.MajoraPacket) {
		defer func(start time.Time) {
			log.Latency().Debugf("%d handleConnect total latency %v", packet.SerialNumber, time.Since(start))
		}(time.Now())
		if session.IsClosed() {
			log.Event().Warnf("[handleConnect] %d -> nat server is closed", packet.SerialNumber)
			client.closeVirtualConnection(session, packet.SerialNumber)
			return
		}
		hostPort := strings.Split(packet.Extra, ":")
		if len(packet.Extra) == 0 || len(hostPort) != 2 {
			log.Event().Errorf("[handleConnect] invalid extra %s", packet.Extra)
			client.closeVirtualConnection(session, packet.SerialNumber)
			return
		}

		dialer := net.Dialer{
			Timeout:   common.UpstreamTimeout,
			LocalAddr: client.localAddr,
		}

		conn, err := dialer.Dial(common.TCP, packet.Extra)
		if err != nil {
			log.Event().Errorf("[handleConnect] %d->connect to %s->%s", packet.SerialNumber, packet.Extra, err.Error())
			client.closeVirtualConnection(session, packet.SerialNumber)
			return
		}

		tcpConn := conn.(*net.TCPConn)
		_ = tcpConn.SetNoDelay(true)
		_ = tcpConn.SetKeepAlive(true)
		client.AddConnection(packet, tcpConn, packet.Extra)
		majoraPacket := protocol.TypeConnectReady.CreatePacket()
		majoraPacket.SerialNumber = packet.SerialNumber
		majoraPacket.Extra = client.config.ClientID
		if session.IsClosed() {
			log.Event().Warnf("[handleConnect] %d -> nat server is closed", packet.SerialNumber)
			client.closeVirtualConnection(session, packet.SerialNumber)
			return
		}
		if _, _, err := session.WritePkg(majoraPacket, 0); err != nil {
			log.Event().Errorf("[handleConnect] %d->write pkg to nat server with error %s", packet.SerialNumber,
				err.Error())
			client.closeVirtualConnection(session, packet.SerialNumber)
			return
		} else {
			log.Event().Debugf("[handleConnect] %d->connect success to %s ", packet.SerialNumber, packet.Extra)
		}
		client.handleUpStream(tcpConn, packet, session)
	}(packet)
}

func (client *Client) handleTransfer(packet *protocol.MajoraPacket, session getty.Session) {
	go func(packet *protocol.MajoraPacket) {
		defer func(start time.Time) {
			log.Latency().Debugf("%d handleTransfer total latency %v", packet.SerialNumber, time.Since(start))
		}(time.Now())
		load, ok := client.connStore.Load(packet.SerialNumber)
		if !ok {
			log.Event().Errorf("[handleTransfer] %d-> can not find connection", packet.SerialNumber)
			client.closeVirtualConnection(session, packet.SerialNumber)
			return
		}
		conn := load.(*net.TCPConn)
		cnt, err := conn.Write(packet.Data)

		if err != nil {
			log.Event().Errorf("[handleTransfer] %d->write to upstream fail for %s", packet.SerialNumber, err)
			client.closeVirtualConnection(session, packet.SerialNumber)
			return
		}

		if cnt != len(packet.Data) {
			log.Event().Errorf("[handleTransfer] %d-> write not all data for expect->%d/%d",
				packet.SerialNumber, len(packet.Data), cnt)
			client.closeVirtualConnection(session, packet.SerialNumber)
			return
		}
		log.Event().Debugf("[handleTransfer] %d-> success %+v", packet.SerialNumber, string(packet.Data))
	}(packet)
}

func (client *Client) handleUpStream(conn *net.TCPConn, packet *protocol.MajoraPacket, session getty.Session) {
	go func() {
		log.Event().Debugf("[handleUpStream] %d-> handleUpStream start...", packet.SerialNumber)
		defer func(start time.Time) {
			_ = conn.Close()
			log.Latency().Debugf("%d handleUpStream total latency %v", packet.SerialNumber, time.Since(start))
		}(time.Now())
		for {
			buf := make([]byte, common.BufSize) // 4k
			cnt, err := conn.Read(buf)
			if err != nil {
				log.Event().Debugf("[handleUpStream] %d->read with error:%+v,l:%s->r:%s",
					packet.SerialNumber, err, conn.LocalAddr(), conn.RemoteAddr())
				client.OnClose(session, conn, packet.SerialNumber)
				break
			}
			pack := protocol.TypeTransfer.CreatePacket()
			pack.Data = buf[:cnt]
			pack.SerialNumber = packet.SerialNumber

			if _, _, err := session.WritePkg(pack, 0); err != nil {
				log.Event().Errorf("[handleUpStream] %d-> write to server fail %+v", packet.SerialNumber, err.Error())
				client.OnClose(session, conn, packet.SerialNumber)
				break
			} else {
				log.Event().Debugf("[handleUpStream] %d->success %+v", packet.SerialNumber, string(packet.Data))
			}
		}
	}()

}

func (client *Client) handleDisconnectMessage(session getty.Session, packet *protocol.MajoraPacket) {
	go func() {
		log.Event().Debugf("[handleDisconnectMessage] %d->session closesd %v", packet.SerialNumber, session.IsClosed())
		if conn, ok := client.connStore.Load(packet.SerialNumber); ok {
			client.OnClose(session, conn.(net.Conn), packet.SerialNumber)
		}
	}()
}

func (client *Client) handleControlMessage(_ *protocol.MajoraPacket) {
	go func() {
		log.Event().Debugf("handleControlMessage")
	}()
}

// handleDestroyMessage 是直接关闭nat server ?
func (client *Client) handleDestroyMessage() {
	go func() {
		client.natTunnel.Close()
	}()
}

func (client *Client) AddConnection(packet *protocol.MajoraPacket, conn *net.TCPConn, addr string) {
	if _, ok := client.connStore.Load(packet.SerialNumber); ok {
		log.Event().Errorf("[AddConnection] %d->error, has one", packet.SerialNumber)
	}
	client.connStore.Store(packet.SerialNumber, conn)
	log.Event().Debugf("[AddConnection] %d->%s success", packet.SerialNumber, addr)
}

// OnClose 1. 本地缓存删除 2. 关闭连接  3. 通知natserver
func (client *Client) OnClose(netSession getty.Session, upStreamSession net.Conn, serialNumber int64) {
	defer func() {
		if err := recover(); err != nil {
			log.Event().Errorf("OnClose %+v", err)
		}
	}()
	client.connStore.Delete(serialNumber)
	_ = upStreamSession.Close()
	client.closeVirtualConnection(netSession, serialNumber)
}

//closeVirtualConnection disconnect to server
func (client *Client) closeVirtualConnection(session getty.Session, serialNumber int64) {
	log.Event().Debugf("[closeVirtualConnection] %d->session closed %v", serialNumber, session.IsClosed())
	if session.IsClosed() {
		log.Event().Warnf("[closeVirtualConnection] %d->session is closed", serialNumber)
		return
	}
	majoraPacket := protocol.TypeDisconnect.CreatePacket()
	majoraPacket.SerialNumber = serialNumber
	majoraPacket.Extra = client.config.ClientID
	if allCnt, sendCnt, err := session.WritePkg(majoraPacket, 0); err != nil {
		log.Event().Warnf("[closeVirtualConnection] ->%d error %s session closed %v allCnt %d sendCnt %d",
			serialNumber, err.Error(), session.IsClosed(), allCnt, sendCnt)
	}
}

func (client *Client) CloseAll(session getty.Session) {
	defer func() {
		if err := recover(); err != nil {
			log.Event().Errorf("OnClose %+v", err)
		}
	}()
	client.connStore.Range(func(key, value interface{}) bool {
		serialNumber := key.(int64)
		conn, _ := value.(*net.TCPConn)
		log.Event().Debugf("[CloseAll] close serialNumber -> %d", serialNumber)
		client.OnClose(session, conn, serialNumber)
		return true
	})
	client.connStore = sync.Map{}
}
