package p2p

import (
	"fmt"
	"net"
	"testing"
	"time"

	cfg "github.com/vapor/config"
	"github.com/vapor/consensus"
	conn "github.com/vapor/p2p/connection"
	"github.com/vapor/p2p/signlib"
	"github.com/vapor/version"
)

const testCh = 0x01

func TestPeerBasic(t *testing.T) {
	privKey, err := signlib.NewPrivKey()
	if err != nil {
		t.Fatal(err)
	}

	// simulate remote peer
	rp := &remotePeer{PrivKey: privKey, Config: testCfg}
	rp.Start()
	defer rp.Stop()

	p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), cfg.DefaultP2PConfig())
	if err != nil {
		t.Fatal(err)
	}
	_, err = p.Start()
	if err != nil {
		t.Fatal(err)
	}
	defer p.Stop()
}

func TestPeerSend(t *testing.T) {
	config := testCfg
	privKey, err := signlib.NewPrivKey()
	if err != nil {
		t.Fatal(err)
	}
	// simulate remote peer
	rp := &remotePeer{PrivKey: privKey, Config: config}
	rp.Start()
	defer rp.Stop()

	p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), config.P2P)
	if err != nil {
		t.Fatal(err)
	}

	_, err = p.Start()
	if err != nil {
		t.Fatal(err)
	}

	defer p.Stop()
	if ok := p.CanSend(testCh); !ok {
		t.Fatal("TestPeerSend send err")
	}

	if ok := p.TrySend(testCh, []byte("test date")); !ok {
		t.Fatal("TestPeerSend try send err")
	}
}

func createOutboundPeerAndPerformHandshake(
	addr *NetAddress,
	config *cfg.P2PConfig,
) (*Peer, error) {
	chDescs := []*conn.ChannelDescriptor{
		{ID: testCh, Priority: 1},
	}
	reactorsByCh := map[byte]Reactor{testCh: NewTestReactor(chDescs, true)}
	privKey, err := signlib.NewPrivKey()
	if err != nil {
		return nil, err
	}
	peerConfig := DefaultPeerConfig(config)
	pc, err := newOutboundPeerConn(addr, privKey, peerConfig)
	if err != nil {
		return nil, err
	}
	nodeInfo, err := pc.HandshakeTimeout(&NodeInfo{
		Moniker: "host_peer",
		Network: "testing",
		Version: "123.123.123",
	}, 5*time.Second)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	p := newPeer(pc, nodeInfo, reactorsByCh, chDescs, nil, false)
	return p, nil
}

type remotePeer struct {
	PrivKey    signlib.PrivKey
	Config     *cfg.Config
	addr       *NetAddress
	quit       chan struct{}
	listenAddr string
}

func (rp *remotePeer) Addr() *NetAddress {
	return rp.addr
}

func (rp *remotePeer) Start() {
	if rp.listenAddr == "" {
		rp.listenAddr = "127.0.0.1:0"
	}

	l, e := net.Listen("tcp", rp.listenAddr) // any available address
	if e != nil {
		fmt.Println("net.Listen tcp :0:", e)
	}
	rp.addr = NewNetAddress(l.Addr())
	rp.quit = make(chan struct{})
	go rp.accept(l)
}

func (rp *remotePeer) Stop() {
	close(rp.quit)
}

func (rp *remotePeer) accept(l net.Listener) {
	conns := []net.Conn{}

	for {
		conn, err := l.Accept()
		if err != nil {
			fmt.Println("Failed to accept conn:", err)
		}

		pc, err := newInboundPeerConn(conn, rp.PrivKey, rp.Config.P2P)
		if err != nil {
			fmt.Println("Failed to create a peer:", err)
		}

		_, err = pc.HandshakeTimeout(&NodeInfo{
			PubKey:      rp.PrivKey.XPub().String(),
			Moniker:     "remote_peer",
			Network:     rp.Config.ChainID,
			Version:     version.Version,
			ListenAddr:  l.Addr().String(),
			ServiceFlag: consensus.DefaultServices,
		}, 5*time.Second)
		if err != nil {
			fmt.Println("Failed to perform handshake:", err)
		}
		conns = append(conns, conn)
		select {
		case <-rp.quit:
			for _, conn := range conns {
				if err := conn.Close(); err != nil {
					fmt.Println(err)
				}
			}
			return
		default:
		}
	}
}

type inboundPeer struct {
	PrivKey signlib.PrivKey
	config  *cfg.Config
}

func (ip *inboundPeer) dial(addr *NetAddress) {
	pc, err := newOutboundPeerConn(addr, ip.PrivKey, DefaultPeerConfig(ip.config.P2P))
	if err != nil {
		fmt.Println("newOutboundPeerConn:", err)
		return
	}

	_, err = pc.HandshakeTimeout(&NodeInfo{
		PubKey:     ip.PrivKey.XPub().String(),
		Moniker:    "remote_peer",
		Network:    ip.config.ChainID,
		Version:    version.Version,
		ListenAddr: addr.String(),
	}, 5*time.Second)
	if err != nil {
		fmt.Println("Failed to perform handshake:", err)
		return
	}
	time.AfterFunc(10*time.Second, pc.CloseConn)
}
