package client

import (
	"errors"
	"fmt"
	"github.com/adamweixuan/getty"
	"net"
	"sync"
	"time"
	"virjar.com/majora-go/common"
	"virjar.com/majora-go/log"
	"virjar.com/majora-go/protocol"
	"virjar.com/majora-go/trace"
)

type TransferListener interface {
	OnUpStreamConnectSuccess(t *Transfer)
	OnUpStreamConnectFailed(t *Transfer, err error)
	OnUpStreamWriteError(t *Transfer, err error)
	OnUpStreamReadError(t *Transfer, err error)
	OnDownStreamWriteError(t *Transfer, err error)
}

type Transfer struct {
	serialNumber int64
	target       string
	upstreamConn *net.TCPConn
	session      getty.Session
	recorder     trace.Recorder
	transferChan chan []byte
	once         sync.Once
	listener     TransferListener
	cancel       chan struct{}
}

func NewTransfer(serialNumber int64, target string, session getty.Session, listener TransferListener, recorder trace.Recorder) *Transfer {
	return &Transfer{
		serialNumber: serialNumber,
		target:       target,
		session:      session,
		recorder:     recorder,
		listener:     listener,
		transferChan: make(chan []byte, 10),
		once:         sync.Once{},
		cancel:       make(chan struct{}, 0),
	}

}

func (t *Transfer) transferToUpstream(data []byte) {
	cnt, err := t.upstreamConn.Write(data)
	if err != nil {
		log.Error().Errorf("[handleTransfer] %d->write to upstream fail for %s", t.serialNumber, err)
		traceMessage := fmt.Sprintf("Write to upstream failed (%d)", t.serialNumber)
		t.recorder.RecordErrorEvent(trace.TransferEvent, traceMessage, err)
		t.listener.OnUpStreamWriteError(t, err)
		return
	}

	if cnt != len(data) {
		log.Error().Errorf("[handleTransfer] %d-> write not all data for expect->%d/%d",
			t.session, len(data), cnt)
		traceMessage := fmt.Sprintf("Write not all data for expect -> %d/%d (sn:%d)", len(data), cnt, t.serialNumber)
		t.recorder.RecordErrorEvent(trace.TransferEvent, traceMessage, nil)
		t.listener.OnUpStreamWriteError(t, err)
		return
	}
	log.Run().Debugf("[handleTransfer] %d-> success dataLen: %d", t.serialNumber, len(data))
	traceMessage := fmt.Sprintf("transfer data success (%d)", t.serialNumber)
	t.recorder.RecordEvent(trace.TransferEvent, traceMessage)
}

func (t *Transfer) transferToDownStream() {
	_ = taskPool.Submit(func() {
		traceRecorder := t.recorder
		traceRecorder.RecordEvent(trace.UpStreamEvent, fmt.Sprintf("Ready read from upstream (sn:%d)", t.serialNumber))
		log.Run().Debugf("[handleUpStream] %d-> handleUpStream start...", t.serialNumber)
		for {
			buf := make([]byte, common.BufSize)
			cnt, err := t.upstreamConn.Read(buf)
			if t.session.IsClosed() {
				t.recorder.RecordErrorEvent(trace.UpStreamEvent, fmt.Sprintf("DownStream closed(sn:%d)", t.serialNumber),
					errors.New("closed"))
				return
			}
			if err != nil {
				log.Run().Debugf("[handleUpStream] %d->read with error:%+v,l:%s->r:%s",
					t.serialNumber, err, t.upstreamConn.LocalAddr(), t.upstreamConn.RemoteAddr())
				recorderMessage := fmt.Sprintf("Read with l:%s->r:%s (sn:%d) ",
					t.upstreamConn.LocalAddr(), t.upstreamConn.RemoteAddr(), t.serialNumber)
				t.recorder.RecordErrorEvent(trace.UpStreamEvent, recorderMessage, err)
				t.listener.OnUpStreamReadError(t, err)
				break
			} else {
				t.recorder.RecordEvent(trace.UpStreamEvent, fmt.Sprintf("read count: %d (sn:%d)",
					cnt, t.serialNumber))
				t.recorder.RecordEvent(trace.UpStreamEvent, fmt.Sprintf("Start write to natServer (sn:%d)", t.serialNumber))
				pack := protocol.TypeTransfer.CreatePacket()
				pack.Data = buf[0:cnt]
				pack.SerialNumber = t.serialNumber
				if _, _, err := t.session.WritePkg(pack, 0); err != nil {
					log.Error().Errorf("[handleUpStream] %d-> write to server fail %+v", t.serialNumber, err.Error())
					t.recorder.RecordErrorEvent(trace.UpStreamEvent,
						fmt.Sprintf("Write to natServer failed (sn:%d)", t.serialNumber), err)
					t.listener.OnDownStreamWriteError(t, err)
				} else {
					log.Run().Debugf("[handleUpStream] %d->success dataLen:%d ", t.serialNumber, len(pack.Data))
					t.recorder.RecordEvent(trace.UpStreamEvent,
						fmt.Sprintf("Write to natServer success(sn:%d)", pack.SerialNumber))
				}
			}

		}
	})
}

func (t *Transfer) TransferToUpstream(data []byte) {
	t.transferChan <- data
}

func (t *Transfer) Start() {

	dialer := net.Dialer{
		Timeout: common.UpstreamTimeout,
	}
	conn, err := dialer.Dial(common.TCP, t.target)
	if err != nil {
		log.Error().Errorf("[handleConnect] %d->connect to %s->%s", t.serialNumber, t.target, err.Error())
		t.recorder.RecordErrorEvent(trace.ConnectEvent,
			fmt.Sprintf("Connect to %s failed (sn:%d)", t.target, t.serialNumber), err)
		t.listener.OnUpStreamConnectFailed(t, err)
		return
	}
	t.upstreamConn = conn.(*net.TCPConn)
	_ = t.upstreamConn.SetDeadline(time.Now().Add(45 * time.Second))
	_ = t.upstreamConn.SetNoDelay(true)
	_ = t.upstreamConn.SetKeepAlive(true)
	t.listener.OnUpStreamConnectSuccess(t)

	_ = taskPool.Submit(func() {
		for {
			select {
			case data := <-t.transferChan:
				t.transferToUpstream(data)
			case <-t.cancel:
				return
			}
		}
	})

	_ = taskPool.Submit(t.transferToDownStream)
}

func (t *Transfer) Close() {
	t.once.Do(func() {
		err := t.upstreamConn.Close()
		if err != nil {
			t.recorder.RecordErrorEvent(trace.DisconnectEvent,
				fmt.Sprintf("Set upstream read deadline failed (sn:%d)", t.serialNumber), err)
		}
		close(t.cancel)
	})
}
