/**
 * @Author: LiXiang
 * @Data: 2019/10/24 下午2:34
 */
package tpro

import (
	"fmt"
	"io"
	"net"
	"strings"

	"github.com/golang/protobuf/proto"
)

type TPServer interface {
	Server(listener net.Listener) error
	SetHandle(func(ctx *ProtoContext, remoteAddr string, sessionId string) *ProtoContext) // TODO 换成ctx
	SetDisconnectCallBack(func(session TPSession) error)
	SetConnectCallBack(func(session TPSession) error)
	Close() error
}

func NewTProServer(opt ...Option) TPServer {
	tOption := newDefaultOption()
	for _, o := range opt {
		o(tOption)
	}
	server := &tpServer{}
	var err error
	if tOption.ingoreAddr != nil {
		server.ingoreAddr, err = ParseCIDR(tOption.ingoreAddr)
		if err != nil {
			server.ingoreAddr = GetAliyunSLBCIDR()
		}
	} else {
		server.ingoreAddr = GetAliyunSLBCIDR()
	}
	return server
}

type tpServer struct {
	l                  net.Listener
	ingoreAddr         []*net.IPNet
	handle             func(ctx *ProtoContext, remoteAddr string, sessionId string) *ProtoContext
	connectCallBack    func(session TPSession) error
	disconnectCallBack func(session TPSession) error
}

func (t *tpServer) Server(listener net.Listener) error {
	t.l = listener
	for {
		conn, err := t.l.Accept()
		if err != nil {
			fmt.Printf("tpro:listener is err ,%v", err)
			return err
		}

		go t.doAccept(conn)

	}
}

// 判断是否是SLB的健康检查 (阿里slb的健康检查会快速三次握手后发送reset包，这里是专门忽略这种错误)
func (t *tpServer) isSLBCheck(err error) bool {
	addrList, isReset := IsResetErr(err)
	if !isReset {
		return false
	}
	destIp := net.ParseIP(addrList.Dest)
	if destIp == nil {
		return false
	}
	for _, ipNet := range t.ingoreAddr {
		if ipNet.Contains(destIp) {
			return true
		}
	}
	return false
}

func (t *tpServer) doAccept(conn net.Conn) {
	var err error
	defer func() {
		if pErr := recover(); pErr != nil {
			// TODO  发送给客户端感知
			fmt.Println("TPro:", pErr)
		}

	}()

	tproContext, err := NewPContextForStream(conn)
	if err != nil {
		if !strings.Contains(err.Error(), "connection reset by peer"){
			fmt.Printf("tpro:read from conn is fail %v \n", err)
		}

		//	 TODO 和客户端约定一个返回这个错误或者直接日志纪录
		return
	}
	if tproContext.SeqId != 0 { // 第一次交互的时候seqid不为0
		fmt.Println("tpro: first coon seqId is not zero")
		// 第一次交互 seq和proto预期值不一样没 TODO 客户端暂时不需要返回信息
		return
	}
	request := &ConnRequest{}
	if err = proto.Unmarshal(tproContext.Msg, request); err != nil {
		fmt.Printf("tpro: unmarshal fail,msg:%v ,err:%v\n", tproContext.Msg, err)
		return
	}
	sessionId, ok := request.Data["role_id"]
	if !ok {
		fmt.Printf("tpro: first conn role_id is not exist\n")
		return
	}
	tproContext.Type = PTResponse
	tproContext.Msg, err = proto.Marshal(&ConnResponse{
		Code: 0,
		Info: "conn success",
	})
	if err = tproContext.EncodeToStream(conn); err != nil {
		fmt.Printf("tpro:firsr conn,response is err,%v ", err) // 发送失败只能本地打日志
		return
	}
	remoteAddr := conn.RemoteAddr().String()

	if err := t.connectCallBack(NewTPSession(conn, sessionId)); err != nil {
		fmt.Println("TPro:", err)
		return
	}
	for {
		p, err := NewPContextForStream(conn)
		if err == io.EOF { // 当客户端调用sendAndClose 断开连接时会收到EOF
			if err = t.disconnectCallBack(NewTPSession(conn, sessionId)); err != nil {
				fmt.Println("Tpro:", err)
			}
			err = conn.Close()
			return
		}
		if err != nil { // 客户端调用close后会触发
			// TODO 错误处理
			if err = t.disconnectCallBack(NewTPSession(conn, sessionId)); err != nil {
				fmt.Println("Tpro:", err)
			}
			err = conn.Close()
			return
		}
		if p.Type == PTHeartBeat {
			err = p.EncodeToStream(conn)
			if err != nil {
				fmt.Println("TPro: send heart beat err:", err) // 发送失败只能本地打印
			}
			continue
		}
		result := t.handle(p, remoteAddr, sessionId)
		result.Type = PTResponse
		result.SeqId = p.SeqId
		if err = result.EncodeToStream(conn); err != nil {
			fmt.Println("TPro: send msg err:", err) // 发送失败只能本地打印
		}
	}

}
func (t *tpServer) SetHandle(f func(ctx *ProtoContext, remoteAddr string, sessoionId string) *ProtoContext) {
	t.handle = f
	// atomic.StoreUintptr((*uintptr)(unsafe.Pointer(&t.handle)), *(*uintptr)(unsafe.Pointer(&f)))
}

func (t *tpServer) SetDisconnectCallBack(f func(session TPSession) error) {
	t.disconnectCallBack = f
}

func (t *tpServer) SetConnectCallBack(f func(session TPSession) error) {
	t.connectCallBack = f
}

func (t *tpServer) Close() error {
	return t.l.Close()
}

/*
	debug使用的服务
*/

func NewDebugTProServer(f func(str string)) TPServer {
	return &tpDebugServer{debugDebugFun: f}
}

type tpDebugServer struct {
	l                  net.Listener
	ingoreAddr         []*net.IPNet
	debugDebugFun      func(str string)
	handle             func(ctx *ProtoContext, remoteAddr string, sessionId string) *ProtoContext
	connectCallBack    func(session TPSession) error
	disconnectCallBack func(session TPSession) error
}

func (t *tpDebugServer) isSLBCheck(err error) bool {
	addrList, isReset := IsResetErr(err)
	if !isReset {
		return false
	}

	destIp := net.ParseIP(addrList.Dest)
	if destIp == nil {
		return false
	}
	for _, ipNet := range t.ingoreAddr {
		if ipNet.Contains(destIp) {
			return true
		}
	}
	return false
}
func (t *tpDebugServer) doDebugAccept(conn net.Conn) {
	result, b, err := NewDebugBytesForStream(conn)
	defer func() {
		if pErr := recover(); pErr != nil {
			// TODO  发送给客户端感知
			t.debugDebugFun(fmt.Sprintf("TPro:%v", pErr))
		}
	}()
	t.debugDebugFun(fmt.Sprintf("TPro: resv msg :%v", result))
	if err != nil && !b {
		if !strings.Contains(err.Error(), "connection reset by peer") {
			t.debugDebugFun(fmt.Sprintf("tpro:read from conn is fail %v ,msg:%v\n", err, result))
		}
		conn.Close() // 无法解析出proto和seq TODO 和客户端约定一个返回这个错误或者直接日志纪录
		return
	}
	// 测试使用的对象 就不池化了
	tproContext := &ProtoContext{}
	if err := tproContext.Decode(result); err != nil {
		t.debugDebugFun(fmt.Sprintf("tpro:decode msg fail %v \n,msg:%v\n", err, result))
		conn.Close() // 无法解析出proto和seq
		return
	}
	if tproContext.SeqId != 0 { // 第一次交互的时候seqid不为0
		t.debugDebugFun(fmt.Sprintf("tpro: first coon seqId is not zero,msg:%v", result))
		conn.Close()
		return
	}
	request := &ConnRequest{}
	if err = proto.Unmarshal(tproContext.Msg, request); err != nil {
		t.debugDebugFun(fmt.Sprintf("tpro:proto unmarshal fail,msg:%v ,err:%v\n", tproContext.Msg, err))
		conn.Close()
		return
	}
	sessionId, ok := request.Data["role_id"]
	if !ok {

		t.debugDebugFun(fmt.Sprintf("tpro: first conn role_id is not exist\n"))
		return
	}
	tproContext.Type = PTResponse
	tproContext.Msg, err = proto.Marshal(&ConnResponse{
		Code: 0,
		Info: "conn success",
	})
	if err = tproContext.EncodeToStream(conn); err != nil {
		t.debugDebugFun(fmt.Sprintf("tpro:firsr conn,response is err,%v ", err)) // 发送失败只能本地打日志
		return
	}

	remoteAddr := conn.RemoteAddr().String()

	if err := t.connectCallBack(NewTPSession(conn, sessionId)); err != nil {
		t.debugDebugFun(fmt.Sprintf("TPro: connect call back error%v", err))
		return
	}
	for {
		result, b, err := NewDebugBytesForStream(conn)
		t.debugDebugFun(fmt.Sprintf("TPro: resv msg :%v", result))
		if err == io.EOF { // 当客户端调用sendAndClose 断开连接时会收到EOF
			if err = t.disconnectCallBack(NewTPSession(conn, sessionId)); err != nil {
				t.debugDebugFun(fmt.Sprintf("TPro: disconnect call back error%v", err))
			}
			err = conn.Close()
			return
		}
		if b {
			continue
		}
		if err != nil {
			t.debugDebugFun(fmt.Sprintf("tpro:read from conn is fail %v ,msg:%v\n", err, result))
			conn.Close() // 无法解析出proto和seq TODO 和客户端约定一个返回这个错误或者直接日志纪录
			continue
		}
		// 测试使用的对象 就不池化了
		p := &ProtoContext{}

		err = p.Decode(result)
		if err != nil { // 客户端调用close后会触发
			// TODO 错误处理
			t.debugDebugFun(fmt.Sprintf("TPro: decode fail ,%v\n,msg:%v", err, result))
			if err = t.disconnectCallBack(NewTPSession(conn, sessionId)); err != nil {
				t.debugDebugFun(fmt.Sprintf("TPro: disconnect call back error%v", err))
			}
			err = conn.Close()
			return

		}
		if p.Type == PTHeartBeat {
			err = p.EncodeToStream(conn)
			if err != nil {
				t.debugDebugFun(fmt.Sprintf("TPro: response PTHeartBeat err: %v", err)) // 发送失败只能本地打印
			}
			continue
		}
		r := t.handle(p, remoteAddr, sessionId)
		r.Type = PTResponse
		r.SeqId = p.SeqId
		if err = r.EncodeToStream(conn); err != nil { // TODO handle的业务流程应该起协程处理
			t.debugDebugFun(fmt.Sprintf("TPro: send msg err: %v", err)) // 发送失败只能本地打印
		}
	}
}

func (t *tpDebugServer) Server(listener net.Listener) error {
	t.l = listener
	for {
		conn, err := t.l.Accept()
		if err != nil {
			t.debugDebugFun(fmt.Sprintf("tpro:listener is err ,%v", err))
			return err
		}
		// mark 临时实现 在改设计
		go t.doDebugAccept(conn)
	}
}

func (t *tpDebugServer) SetHandle(f func(ctx *ProtoContext, remoteAddr string, sessoionId string) *ProtoContext) {
	t.handle = f
	// atomic.StoreUintptr((*uintptr)(unsafe.Pointer(&t.handle)), *(*uintptr)(unsafe.Pointer(&f)))
}

func (t *tpDebugServer) SetDisconnectCallBack(f func(session TPSession) error) {
	t.disconnectCallBack = f
}

func (t *tpDebugServer) SetConnectCallBack(f func(session TPSession) error) {
	t.connectCallBack = f
}

func (t *tpDebugServer) Close() error {
	return t.l.Close()
}
