package bird

import (
	"fmt"
	"log"
	"os"
	"reflect"
	"testing"

	"github.com/kr/pretty"
)

func openFile(filename string) (*os.File, error) {
	sample := "../test/" + filename
	return os.Open(sample)
}

func TestParseStatus(t *testing.T) {
	tests := []struct {
		file     string
		expected Parsed
	}{
		// Test for bird1.x status
		{
			"status1.sample",
			Parsed{
				"current_server": "2021-03-30 02:28:45",
				"last_reboot":    "2021-03-30 02:28:19",
				"last_reconfig":  "2021-03-30 02:28:19",
				"message":        "Daemon is up and running",
				"router_id":      "172.25.3.2",
				"version":        "1.6.6",
			},
		},
		// Test for bird2.x status
		{
			"status2.sample",
			Parsed{
				"current_server": "2021-03-30 02:23:32.330",
				"last_reboot":    "2021-03-30 01:58:07.850",
				"last_reconfig":  "2021-03-30 01:58:07.850",
				"message":        "Daemon is up and running",
				"router_id":      "172.25.3.2",
				"version":        "2.0.7",
			},
		},
		// Test with advanced version
		{
			"status3.sample",
			Parsed{
				"current_server": "2022-06-07 08:28:16",
				"last_reboot":    "2022-06-03 12:35:43",
				"last_reconfig":  "2022-06-03 12:35:43",
				"message":        "Daemon is up and running",
				"router_id":      "1.2.3.4",
				"version":        "v2.0.9-11-g207ac485",
			},
		},
		// Test for bird3.x status
		{
			"status4.sample",
			Parsed{
				"current_server": "2025-03-31 16:39:47.927",
				"last_reboot":    "2025-03-28 05:41:41.348",
				"last_reconfig":  "2025-03-31 12:34:16.644",
				"message":        "Daemon is up and running",
				"router_id": 	  "1.2.3.4",
				"version":        "3.0.1",
			},
		},
	}

	for _, test := range tests {
		f, err := openFile(test.file)
		if err != nil {
			t.Error(err)
		}
		s := parseStatus(f)
		f.Close()
		status := s["status"].(Parsed)
		if !reflect.DeepEqual(status, test.expected) {
			t.Error("Parse status: ", status, "expected: ", test.expected)
		}

	}
}

func TestParseBgpRoutes(t *testing.T) {

	inputs := []string{
		"1 imported, 0 filtered, 2 exported, 1 preferred",
		"0 imported, 2846 exported", // Bird 1.4.x
	}

	expected := []Parsed{
		Parsed{
			"imported":  int64(1),
			"filtered":  int64(0),
			"exported":  int64(2),
			"preferred": int64(1),
		},
		Parsed{
			"imported": int64(0),
			"exported": int64(2846),
		},
	}

	for i, in := range inputs {
		routes := parseProtocolRoutes(in)
		if !reflect.DeepEqual(routes, expected[i]) {
			t.Error("Parse bgpRoutes:", routes, "expected:", expected[i])
		}
	}

}

func TestParseProtocolBgp(t *testing.T) {
	f, err := openFile("protocols_bgp_pipe.sample")
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	p := parseProtocols(f)
	log.Printf("%# v", pretty.Formatter(p))
	protocols := p["protocols"].(Parsed)

	if len(protocols) != 3 {
		//log.Printf("%# v", pretty.Formatter(protocols))
		t.Fatalf("Expected 3 protocols, found: %v", len(protocols))
	}

	fmt.Println(protocols)
}

func TestParseProtocolShort(t *testing.T) {
	f, err := openFile("protocols_short.sample")
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	p := parseProtocolsShort(f)
	log.Printf("%# v", pretty.Formatter(p))
	protocols := p["protocols"].(Parsed)

	if len(protocols) != 27 {
		//log.Printf("%# v", pretty.Formatter(protocols))
		t.Fatalf("Expected 27 protocols, found: %v", len(protocols))
	}

	fmt.Println(protocols)
}

func TestParseProtocolShortBird3(t *testing.T) {
	f, err := openFile("protocols_short_bird3.sample")
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	p := parseProtocolsShort(f)
	log.Printf("%# v", pretty.Formatter(p))
	protocols := p["protocols"].(Parsed)

	if len(protocols) != 22 {
		//log.Printf("%# v", pretty.Formatter(protocols))
		t.Fatalf("Expected 22 protocols, found: %v", len(protocols))
	}

	fmt.Println(protocols)
}

func TestParseRoutesAllIpv4Bird1(t *testing.T) {
	runTestForIpv4WithFile("routes_bird1_ipv4.sample", t)
}

func TestParseRoutesAllIpv4Bird2(t *testing.T) {
	runTestForIpv4WithFile("routes_bird2_ipv4.sample", t)
}

func TestParseRoutesAllIpv4Bird3(t *testing.T) {
	runTestForIpv4WithFile("routes_bird3_ipv4.sample", t)
}

func runTestForIpv4WithFile(file string, t *testing.T) {
	f, err := openFile(file)
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	result := parseRoutes(f)
	log.Printf("%# v", pretty.Formatter(result))
	routes, ok := result["routes"].([]Parsed)
	if !ok {
		t.Fatal("Error getting routes")
	}

	if len(routes) != 4 {
		t.Fatal("Expected 4 routes but got ", len(routes))
	}

	assertRouteIsEqual(expectedRoute{
		network: "16.0.0.0/24",
		gateway: "1.2.3.16",
		asPath:  []string{"1340"},
		community: [][]int64{
			{0, 5464},
			{0, 8339},
			{0, 8741},
			{0, 8823},
			{0, 12387},
			{0, 13101},
			{0, 16097},
			{0, 16316},
			{0, 20546},
			{0, 20686},
			{0, 20723},
			{0, 21083},
			{0, 21385},
			{0, 24940},
			{0, 25504},
			{0, 28876},
			{0, 29545},
			{0, 30058},
			{0, 31103},
			{0, 31400},
			{0, 39090},
			{0, 39392},
			{0, 39912},
			{0, 42473},
			{0, 43957},
			{0, 44453},
			{0, 47297},
			{0, 47692},
			{0, 48200},
			{0, 50629},
			{0, 51191},
			{0, 51839},
			{0, 51852},
			{0, 54113},
			{0, 56719},
			{0, 57957},
			{0, 60517},
			{0, 60574},
			{0, 61303},
			{0, 62297},
			{0, 62336},
			{0, 62359},
			{33891, 33892},
			{33891, 50673},
			{48793, 48793},
			{50673, 500},
			{65101, 11077},
			{65102, 11000},
			{65103, 724},
			{65104, 150},
		},
		largeCommunities: [][]int64{
			{9033, 65666, 12},
			{9033, 65666, 9},
		},
		extendedCommunities: []interface{}{
			[]interface{}{"rt", "42", "1234"},
			[]interface{}{"generic", "0x43000000", "0x1"},
		},
		metric:    100,
		localPref: "100",
		protocol:  "ID8503_AS1340",
		primary:   true,
		iface:     "eno7",
	}, routes[0], "Route 1", t)
	assertRouteIsEqual(expectedRoute{
		network: "200.0.0.0/24",
		gateway: "1.2.3.15",
		asPath:  []string{"1339"},
		community: [][]int64{
			{65011, 40},
			{9033, 3251},
		},
		largeCommunities: [][]int64{
			{9033, 65666, 12},
			{9033, 65666, 9},
		},
		extendedCommunities: []interface{}{
			[]interface{}{"ro", "21414", "52001"},
			[]interface{}{"ro", "21414", "52004"},
			[]interface{}{"ro", "21414", "64515"},
		},
		metric:    100,
		localPref: "100",
		protocol:  "ID8497_AS1339",
		primary:   true,
		iface:     "eno7",
	}, routes[1], "Route 2", t)
	assertRouteIsEqual(expectedRoute{
		network: "200.0.0.0/24",
		gateway: "1.2.3.16",
		asPath:  []string{"1340"},
		community: [][]int64{
			{65011, 3},
			{9033, 3251},
		},
		largeCommunities: [][]int64{
			{9033, 65666, 12},
			{9033, 65666, 9},
		},
		extendedCommunities: []interface{}{
			[]interface{}{"ro", "21414", "52001"},
			[]interface{}{"ro", "21414", "52004"},
			[]interface{}{"ro", "21414", "64515"},
		},
		metric:    100,
		localPref: "100",
		protocol:  "ID8503_AS1340",
		primary:   false,
		iface:     "eno8",
	}, routes[2], "Route 3", t)
	assertRouteIsEqual(expectedRoute{
		network: "16.0.0.0/24",
		gateway: "1.2.3.16",
		asPath:  []string{"1340"},
		community: [][]int64{
			{65011, 3},
			{9033, 3251},
		},
		largeCommunities: [][]int64{
			{9033, 65666, 12},
			{9033, 65666, 9},
		},
		extendedCommunities: []interface{}{
			[]interface{}{"rt", "42", "1234"},
			[]interface{}{"generic", "0x43000000", "0x1"},
		},
		metric:    100,
		localPref: "100",
		protocol:  "ID8503_AS1340",
		primary:   true,
		iface:     "eno7",
	}, routes[3], "Route 4", t)
}

func TestParseRoutesAllIpv6Bird1(t *testing.T) {
	runTestForIpv6WithFile("routes_bird1_ipv6.sample", t)
}

func TestParseRoutesAllIpv6Bird2(t *testing.T) {
	runTestForIpv6WithFile("routes_bird2_ipv6.sample", t)
}

func TestParseRoutesAllIpv6Bird3(t *testing.T) {
	runTestForIpv6WithFile("routes_bird3_ipv6.sample", t)
}

func runTestForIpv6WithFile(file string, t *testing.T) {
	f, err := openFile(file)
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	result := parseRoutes(f)
	log.Printf("%# v", pretty.Formatter(result))
	routes, ok := result["routes"].([]Parsed)
	if !ok {
		t.Fatal("Error getting routes")
	}

	if len(routes) != 3 {
		t.Fatal("Expected 3 routes but got ", len(routes))
	}

	assertRouteIsEqual(expectedRoute{
		network: "2001:4860::/32",
		gateway: "fe80:ffff:ffff::1",
		asPath:  []string{"15169"},
		community: [][]int64{
			{0, 5464},
			{0, 8339},
			{0, 8741},
			{0, 8823},
			{0, 12387},
			{0, 13101},
			{0, 16097},
			{0, 16316},
			{0, 20546},
			{0, 20686},
			{0, 20723},
			{0, 21083},
			{0, 21385},
			{0, 24940},
			{0, 25504},
			{0, 28876},
			{0, 29545},
			{0, 30058},
			{0, 31103},
			{0, 31400},
			{0, 39090},
			{0, 39392},
			{0, 39912},
			{0, 42473},
			{0, 43957},
			{0, 44453},
			{0, 47297},
			{0, 47692},
			{0, 48200},
			{0, 50629},
			{0, 51191},
			{0, 51839},
			{0, 51852},
			{0, 54113},
			{0, 56719},
			{0, 57957},
			{0, 60517},
			{0, 60574},
			{0, 61303},
			{0, 62297},
			{0, 62336},
			{0, 62359},
			{33891, 33892},
			{33891, 50673},
			{48793, 48793},
			{50673, 500},
			{65101, 11077},
			{65102, 11000},
			{65103, 724},
			{65104, 150},
		},
		largeCommunities: [][]int64{
			{48821, 0, 2000},
			{48821, 0, 2100},
		},
		extendedCommunities: []interface{}{
			[]interface{}{"ro", "21414", "52001"},
			[]interface{}{"ro", "21414", "52004"},
			[]interface{}{"ro", "21414", "64515"},
		},
		metric:    100,
		localPref: "500",
		primary:   true,
		protocol:  "upstream1",
		iface:     "eth2",
	}, routes[0], "Route 1", t)
	assertRouteIsEqual(expectedRoute{
		network: "2001:4860::/32",
		gateway: "fe80:ffff:ffff::2",
		asPath:  []string{"50629", "15169"},
		community: [][]int64{
			{50629, 200},
			{50629, 201},
		},
		largeCommunities: [][]int64{
			{48821, 0, 3000},
			{48821, 0, 3100},
		},
		extendedCommunities: []interface{}{
			[]interface{}{"ro", "21414", "52001"},
			[]interface{}{"ro", "21414", "52004"},
			[]interface{}{"ro", "21414", "64515"},
		},
		localPref: "100",
		metric:    100,
		primary:   false,
		protocol:  "upstream2",
		iface:     "eth3",
	}, routes[1], "Route 2", t)
	assertRouteIsEqual(expectedRoute{
		network: "2001:678:1e0::/48",
		gateway: "fe80:ffff:ffff::2",
		asPath:  []string{"202739"},
		community: [][]int64{
			{48821, 2000},
			{48821, 2100},
		},
		largeCommunities: [][]int64{
			{48821, 0, 2000},
			{48821, 0, 2100},
		},
		extendedCommunities: []interface{}{
			[]interface{}{"unknown 0x4300", "0", "1"},
		},
		metric:    100,
		localPref: "5000",
		primary:   true,
		protocol:  "upstream2",
		iface:     "eth2",
	}, routes[2], "Route 3", t)
}

func assertRouteIsEqual(expected expectedRoute, actual Parsed, name string, t *testing.T) {
	if prefix := value(actual, "network", name, t).(string); prefix != expected.network {
		t.Fatal(name, ": Expected network to be:", expected.network, "not", prefix)
	}

	if nextHop := value(actual, "gateway", name, t).(string); nextHop != expected.gateway {
		t.Fatal(name, ": Expected gateway to be:", expected.gateway, "not", nextHop)
	}

	if metric := value(actual, "metric", name, t).(int64); metric != expected.metric {
		t.Fatal(name, ": Expected metric to be:", expected.metric, "not", metric)
	}

	if protocol := value(actual, "from_protocol", name, t).(string); protocol != expected.protocol {
		t.Fatal(name, ": Expected protocol to be:", expected.protocol, "not", protocol)
	}

	if iface := value(actual, "interface", name, t).(string); iface != expected.iface {
		t.Fatal(name, ": Expected interface to be:", expected.iface, "not", iface)
	}

	bgp := actual["bgp"].(Parsed)
	if localPref := value(bgp, "local_pref", name, t).(string); localPref != expected.localPref {
		t.Fatal(name, ": Expected local_pref to be:", expected.localPref, "not", localPref)
	}

	if asPath := value(bgp, "as_path", name, t).([]string); !reflect.DeepEqual(asPath, expected.asPath) {
		t.Fatal(name, ": Expected as_path to be:", expected.asPath, "not", asPath)
	}

	if community := value(bgp, "communities", name, t).([][]int64); !reflect.DeepEqual(community, expected.community) {
		t.Fatal(name, ": Expected community to be:", expected.community, "not", community)
	}

	if largeCommunity := value(bgp, "large_communities", name, t).([][]int64); !reflect.DeepEqual(largeCommunity, expected.largeCommunities) {
		t.Fatal(name, ": Expected large_community to be:", expected.largeCommunities, "not", largeCommunity)
	}

	if extendedCommunity, ok := bgp["ext_communities"]; ok {
		if !reflect.DeepEqual(extendedCommunity.([]interface{}), expected.extendedCommunities) {
			t.Fatal(name, ": Expected ext_community to be:", expected.extendedCommunities, "not", extendedCommunity)
		}
	}
}

func value(parsed Parsed, key, name string, t *testing.T) interface{} {
	v, ok := parsed[key]
	if !ok {
		t.Fatal(name, ": Key not found", key)
	}

	return v
}

type expectedRoute struct {
	network             string
	gateway             string
	asPath              []string
	community           [][]int64
	largeCommunities    [][]int64
	extendedCommunities []interface{}
	metric              int64
	protocol            string
	primary             bool
	localPref           string
	iface               string
}
