package server

import (
	"net"
	"testing"
	"time"

	bnet "github.com/bio-routing/bio-rd/net"
	"github.com/stretchr/testify/assert"
)

func TestBMPServer(t *testing.T) {
	srv := NewBMPReceiver(BMPReceiverConfig{
		KeepalivePeriod: time.Second,
	})

	rCfg := RouterConfig{
		Passive:        false,
		IgnorePeerASNs: []uint32{13335},
	}
	rtr := newRouter(net.IP{10, 0, 255, 1}, 30119, &adjRIBInFactory{}, rCfg)
	_, pipe := net.Pipe()
	rtr.con = pipe
	srv.routers[rtr.address.String()] = rtr

	init := []byte{
		3,           // Version
		0, 0, 0, 22, // Length
		4, // Msg Type (init)

		0, 1, // SysDescr TLV
		0, 4, // Length
		0x42, 0x42, 0x42, 0x42,

		0, 2, // SysName TLV
		0, 4, // Length
		0x41, 0x41, 0x41, 0x41,
	}
	rtr.processMsg(init)

	peerUpA := []byte{
		3,            // Version
		0, 0, 0, 126, // Length
		3, // Msg Type (peer up)

		0,                        // Peer Type (global instance peer)
		0,                        // Peer Flags
		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 1, 1, // Peer Address (10.1.1.1)
		0, 0, 0, 200, // Peer AS = 200
		0, 0, 0, 200, // Peer BGP ID = 200
		0, 0, 0, 0, // Timestamp seconds
		0, 0, 0, 0, // Timestamp microseconds

		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 1, 2, // Local Address (10.1.1.2)
		0, 222, // Local Port
		0, 179, // Remote Port

		// Sent OPEN
		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 29, // Length
		1,      // Type (OPEN)
		4,      // BGP Version
		0, 100, // ASN
		0, 180, // Hold Time
		1, 0, 0, 100, // BGP ID
		0, // Ops Param Len

		// Received OPEN
		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 29, // Length
		1,      // Type (OPEN)
		4,      // BGP Version
		0, 200, // ASN
		0, 180, // Hold Time
		1, 0, 0, 200, // BGP ID
		0, // Ops Param Len
	}
	rtr.processMsg(peerUpA)

	if srv.GetRouter("NotExistent") != nil {
		t.Errorf("GetRouter() returned a non-existent router")
		return
	}

	aaaa := srv.GetRouter("10.0.255.1").(*Router)
	if aaaa == nil {
		t.Errorf("Router AAAA not found")
		return
	}

	aaaaVRFs := aaaa.GetVRFs()
	if len(aaaaVRFs) != 1 {
		t.Errorf("Unexpected VRF count for router AAAA: %d", len(aaaaVRFs))
		return
	}

	peerUpB := []byte{
		3,            // Version
		0, 0, 0, 126, // Length
		3, // Msg Type (peer up)

		0,                        // Peer Type (global instance peer)
		0,                        // Peer Flags
		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1, // Peer Address (10.1.2.1)
		0, 0, 0, 222, // Peer AS = 222
		0, 0, 0, 222, // Peer BGP ID = 222
		0, 0, 0, 0, // Timestamp seconds
		0, 0, 0, 0, // Timestamp microseconds

		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 2, // Local Address (10.1.2.2)
		0, 222, // Local Port
		0, 179, // Remote Port

		// Sent OPEN
		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 29, // Length
		1,      // Type (OPEN)
		4,      // BGP Version
		0, 100, // ASN
		0, 180, // Hold Time
		1, 0, 0, 100, // BGP ID
		0, // Ops Param Len

		// Received OPEN
		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 29, // Length
		1,      // Type (OPEN)
		4,      // BGP Version
		0, 222, // ASN
		0, 180, // Hold Time
		1, 0, 0, 222, // BGP ID
		0, // Ops Param Len
	}
	rtr.processMsg(peerUpB)

	aaaaVRFs = aaaa.GetVRFs()
	if len(aaaaVRFs) != 1 {
		t.Errorf("Unexpected VRF count for router AAAA: %d", len(aaaaVRFs))
		return
	}

	peerUpC := []byte{
		3,            // Version
		0, 0, 0, 126, // Length
		3, // Msg Type (peer up)

		0,                      // Peer Type (global instance peer)
		0,                      // Peer Flags
		0, 0, 0, 0, 0, 0, 0, 0, // Peer Distinguisher
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1, // Peer Address (10.1.3.1)
		0, 0, 0, 233, // Peer AS = 233
		0, 0, 0, 233, // Peer BGP ID = 233
		0, 0, 0, 0, // Timestamp seconds
		0, 0, 0, 0, // Timestamp microseconds

		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 2, // Local Address (10.1.3.2)
		0, 222, // Local Port
		0, 179, // Remote Port

		// Sent OPEN
		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 29, // Length
		1,      // Type (OPEN)
		4,      // BGP Version
		0, 100, // ASN
		0, 180, // Hold Time
		1, 0, 0, 100, // BGP ID
		0, // Ops Param Len

		// Received OPEN
		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 29, // Length
		1,      // Type (OPEN)
		4,      // BGP Version
		0, 233, // ASN
		0, 180, // Hold Time
		1, 0, 0, 222, // BGP ID
		0, // Ops Param Len
	}
	rtr.processMsg(peerUpC)

	peerUpD := []byte{
		3,            // Version
		0, 0, 0, 126, // Length
		3, // Msg Type (peer up)

		0,                      // Peer Type (global instance peer)
		0,                      // Peer Flags
		0, 0, 0, 0, 0, 0, 0, 0, // Peer Distinguisher
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 100, // Peer Address (10.1.2.100)
		0, 0, 52, 23, // Peer AS = 13335
		0, 0, 0, 233, // Peer BGP ID = 240
		0, 0, 0, 0, // Timestamp seconds
		0, 0, 0, 0, // Timestamp microseconds

		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 200, // Local Address (10.1.2.200)
		0, 222, // Local Port
		0, 179, // Remote Port

		// Sent OPEN
		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 29, // Length
		1,      // Type (OPEN)
		4,      // BGP Version
		0, 100, // ASN
		0, 180, // Hold Time
		1, 0, 0, 100, // BGP ID
		0, // Ops Param Len

		// Received OPEN
		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 29, // Length
		1,      // Type (OPEN)
		4,      // BGP Version
		0, 233, // ASN
		0, 180, // Hold Time
		1, 0, 0, 222, // BGP ID
		0, // Ops Param Len
	}
	rtr.processMsg(peerUpD)

	assert.Equal(t, rtr.ignoredPeers, map[bnet.IP]struct{}{
		bnet.IPv4FromOctets(10, 1, 2, 100): {},
	})

	updateD1 := []byte{
		3,            // Version
		0, 0, 0, 100, // Length
		0, // Msg Type (route monitoring)

		0,                        // Peer Type (global instance peer)
		0b01100000,               // Peer Flags
		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 100, // Peer Address (10.1.2.100)
		0, 0, 0, 200, // Peer AS = 200
		0, 0, 0, 200, // Peer BGP ID = 200
		0, 0, 0, 0, // Timestamp seconds
		0, 0, 0, 0, // Timestamp microseconds

		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 52, // Length
		2, // Type (UPDATE)

		0, 0, // Withdraw length
		0, 27, // Total Path Attribute Length

		0, // Attribute flags
		3, // Attribute Type code (Next Hop)
		4, // Length
		10, 0, 0, 0,

		255,  // Attribute flags
		1,    // Attribute Type code (ORIGIN)
		0, 1, // Length
		2, // INCOMPLETE

		0,      // Attribute flags
		2,      // Attribute Type code (AS Path)
		12,     // Length
		2,      // Type = AS_SEQUENCE
		2,      // Path Segment Length
		59, 65, // AS15169
		12, 248, // AS3320
		1,      // Type = AS_SET
		2,      // Path Segment Length
		59, 65, // AS15169
		12, 248, // AS3320

		8, 20, // 20.0.0.0/8
	}
	rtr.processMsg(updateD1)

	aaaaVRFs = aaaa.GetVRFs()
	if len(aaaaVRFs) != 2 {
		t.Errorf("Unexpected VRF count for router AAAA: %d", len(aaaaVRFs))
		return
	}

	peerDownC := []byte{
		3,           // Version
		0, 0, 0, 69, // Length
		2, // Msg Type (peer down)

		0,                      // Peer Type (global instance peer)
		0,                      // Peer Flags
		0, 0, 0, 0, 0, 0, 0, 0, // Peer Distinguisher
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1, // Peer Address (10.1.3.1)
		0, 0, 0, 233, // Peer AS = 233
		0, 0, 0, 233, // Peer BGP ID = 233
		0, 0, 0, 0, // Timestamp seconds
		0, 0, 0, 0, // Timestamp microseconds

		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 2, // Local Address (10.1.3.2)
		0, 222, // Local Port
		0, 179, // Remote Port

		4, // Reason = unexpected termination of transport session
	}
	rtr.processMsg(peerDownC)

	if aaaa.neighborManager.getNeighbor(0, [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1}) != nil {
		t.Errorf("Unexpected neighbor")
		return
	}

	if aaaa.neighborManager.getNeighbor(123, [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1}) == nil {
		t.Errorf("Expected neighbor not found")
		return
	}

	v := aaaa.GetVRF(123)
	lr := v.IPv4UnicastRIB()
	if lr.Count() != 0 {
		t.Errorf("Unexpected route count")
		return
	}

	updateA1 := []byte{
		3,            // Version
		0, 0, 0, 100, // Length
		0, // Msg Type (route monitoring)

		0,                        // Peer Type (global instance peer)
		0b01100000,               // Peer Flags
		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 1, 1, // Peer Address (10.1.1.1)
		0, 0, 0, 200, // Peer AS = 200
		0, 0, 0, 200, // Peer BGP ID = 200
		0, 0, 0, 0, // Timestamp seconds
		0, 0, 0, 0, // Timestamp microseconds

		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 52, // Length
		2, // Type (UPDATE)

		0, 0, // Withdraw length
		0, 27, // Total Path Attribute Length

		0, // Attribute flags
		3, // Attribute Type code (Next Hop)
		4, // Length
		10, 0, 0, 0,

		255,  // Attribute flags
		1,    // Attribute Type code (ORIGIN)
		0, 1, // Length
		2, // INCOMPLETE

		0,      // Attribute flags
		2,      // Attribute Type code (AS Path)
		12,     // Length
		2,      // Type = AS_SEQUENCE
		2,      // Path Segment Length
		59, 65, // AS15169
		12, 248, // AS3320
		1,      // Type = AS_SET
		2,      // Path Segment Length
		59, 65, // AS15169
		12, 248, // AS3320

		8, 10, // 10.0.0.0/8
	}
	rtr.processMsg(updateA1)

	if lr.Count() != 1 {
		t.Errorf("Unexpected route count")
		return
	}

	route := lr.Get(bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8).Ptr())
	if route == nil {
		t.Errorf("Expected route not found")
		return
	}

	peerDownA := []byte{
		3,           // Version
		0, 0, 0, 69, // Length
		2, // Msg Type (peer down)

		0,                        // Peer Type (global instance peer)
		0,                        // Peer Flags
		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 1, 1, // Peer Address (10.1.1.1)
		0, 0, 0, 200, // Peer AS = 200
		0, 0, 0, 200, // Peer BGP ID = 200
		0, 0, 0, 0, // Timestamp seconds
		0, 0, 0, 0, // Timestamp microseconds

		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 1, 2, // Local Address (10.1.1.2)
		0, 222, // Local Port
		0, 179, // Remote Port

		4, // Reason = unexpected termination of transport session
	}
	rtr.processMsg(peerDownA)

	if lr.Count() != 0 {
		t.Errorf("Unexpected route count")
		return
	}

	route = lr.Get(bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8).Ptr())
	if route != nil {
		t.Errorf("Unexpected route found")
		return
	}

	updateB1 := []byte{
		3,            // Version
		0, 0, 0, 100, // Length
		0, // Msg Type (route monitoring)

		0,                        // Peer Type (global instance peer)
		0b01100000,               // Peer Flags
		0, 0, 0, 0, 0, 0, 0, 123, // Peer Distinguisher
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 2, 1, // Peer Address (10.1.2.1)
		0, 0, 0, 222, // Peer AS = 222
		0, 0, 0, 222, // Peer BGP ID = 222
		0, 0, 0, 0, // Timestamp seconds
		0, 0, 0, 0, // Timestamp microseconds

		255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
		0, 52, // Length
		2, // Type (UPDATE)

		0, 0, // Withdraw length
		0, 27, // Total Path Attribute Length

		0, // Attribute flags
		3, // Attribute Type code (Next Hop)
		4, // Length
		10, 0, 0, 0,

		255,  // Attribute flags
		1,    // Attribute Type code (ORIGIN)
		0, 1, // Length
		2, // INCOMPLETE

		0,      // Attribute flags
		2,      // Attribute Type code (AS Path)
		12,     // Length
		2,      // Type = AS_SEQUENCE
		2,      // Path Segment Length
		59, 65, // AS15169
		12, 248, // AS3320
		1,      // Type = AS_SET
		2,      // Path Segment Length
		59, 65, // AS15169
		12, 248, // AS3320

		8, 10, // 10.0.0.0/8
	}
	rtr.processMsg(updateB1)

	if lr.Count() != 1 {
		t.Errorf("Unexpected route count")
		return
	}

	termination := []byte{
		3,           // Version
		0, 0, 0, 11, // Length
		5, // Msg Type (termination)

		0, 1, // Type = Reason
		0, 1, // Length
		0, // Reason = Admin Down
	}
	rtr.processMsg(termination)

	if lr.Count() != 0 {
		t.Errorf("Unexpected route count")
		return
	}
}
