package networkmonitor

import (
	"net"
	"net/netip"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
)

func TestRouteChanged(t *testing.T) {
	tests := []struct {
		name      string
		route     systemops.RouteUpdate
		nexthopv4 systemops.Nexthop
		nexthopv6 systemops.Nexthop
		expected  bool
	}{
		{
			name: "soft interface should be ignored",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteModified,
				Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP: netip.MustParseAddr("192.168.1.1"),
					Intf: &net.Interface{
						Name: "ISATAP-Interface", // isSoftInterface checks name
					},
				},
			},
			nexthopv4: systemops.Nexthop{
				IP: netip.MustParseAddr("192.168.1.2"),
			},
			nexthopv6: systemops.Nexthop{
				IP: netip.MustParseAddr("2001:db8::1"),
			},
			expected: false,
		},
		{
			name: "modified route with different v4 nexthop IP should return true",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteModified,
				Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP: netip.MustParseAddr("192.168.1.1"),
					Intf: &net.Interface{
						Index: 1, Name: "eth0",
					},
				},
			},
			nexthopv4: systemops.Nexthop{
				IP: netip.MustParseAddr("192.168.1.2"),
				Intf: &net.Interface{
					Index: 1, Name: "eth0",
				},
			},
			nexthopv6: systemops.Nexthop{
				IP: netip.MustParseAddr("2001:db8::1"),
			},
			expected: true,
		},
		{
			name: "modified route with same v4 nexthop (IP and Intf Index) should return false",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteModified,
				Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP: netip.MustParseAddr("192.168.1.1"),
					Intf: &net.Interface{
						Index: 1, Name: "eth0",
					},
				},
			},
			nexthopv4: systemops.Nexthop{
				IP: netip.MustParseAddr("192.168.1.1"),
				Intf: &net.Interface{
					Index: 1, Name: "eth0",
				},
			},
			nexthopv6: systemops.Nexthop{
				IP: netip.MustParseAddr("2001:db8::1"),
			},
			expected: false,
		},
		{
			name: "added route with different v6 nexthop IP should return true",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteAdded,
				Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP: netip.MustParseAddr("2001:db8::2"),
					Intf: &net.Interface{
						Index: 1, Name: "eth0",
					},
				},
			},
			nexthopv4: systemops.Nexthop{
				IP: netip.MustParseAddr("192.168.1.1"),
			},
			nexthopv6: systemops.Nexthop{
				IP: netip.MustParseAddr("2001:db8::1"),
				Intf: &net.Interface{
					Index: 1, Name: "eth0",
				},
			},
			expected: true,
		},
		{
			name: "added route with same v6 nexthop (IP and Intf Index) should return false",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteAdded,
				Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP: netip.MustParseAddr("2001:db8::1"),
					Intf: &net.Interface{
						Index: 1, Name: "eth0",
					},
				},
			},
			nexthopv4: systemops.Nexthop{
				IP: netip.MustParseAddr("192.168.1.1"),
			},
			nexthopv6: systemops.Nexthop{
				IP: netip.MustParseAddr("2001:db8::1"),
				Intf: &net.Interface{
					Index: 1, Name: "eth0",
				},
			},
			expected: false,
		},
		{
			name: "deleted route matching tracked v4 nexthop (IP and Intf Index) should return true",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteDeleted,
				Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP: netip.MustParseAddr("192.168.1.1"),
					Intf: &net.Interface{
						Index: 1, Name: "eth0",
					},
				},
			},
			nexthopv4: systemops.Nexthop{
				IP: netip.MustParseAddr("192.168.1.1"),
				Intf: &net.Interface{
					Index: 1, Name: "eth0",
				},
			},
			nexthopv6: systemops.Nexthop{
				IP: netip.MustParseAddr("2001:db8::1"),
			},
			expected: true,
		},
		{
			name: "deleted route not matching tracked v4 nexthop (different IP) should return false",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteDeleted,
				Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP: netip.MustParseAddr("192.168.1.3"), // Different IP
					Intf: &net.Interface{
						Index: 1, Name: "eth0",
					},
				},
			},
			nexthopv4: systemops.Nexthop{
				IP: netip.MustParseAddr("192.168.1.1"),
				Intf: &net.Interface{
					Index: 1, Name: "eth0",
				},
			},
			nexthopv6: systemops.Nexthop{
				IP: netip.MustParseAddr("2001:db8::1"),
			},
			expected: false,
		},
		{
			name: "modified v4 route with same IP, different Intf Index should return true",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteModified,
				Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP:   netip.MustParseAddr("192.168.1.1"),
					Intf: &net.Interface{Index: 2, Name: "eth1"}, // Different Intf Index
				},
			},
			nexthopv4: systemops.Nexthop{
				IP:   netip.MustParseAddr("192.168.1.1"),
				Intf: &net.Interface{Index: 1, Name: "eth0"},
			},
			expected: true,
		},
		{
			name: "modified v4 route with same IP, one Intf nil, other non-nil should return true",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteModified,
				Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP:   netip.MustParseAddr("192.168.1.1"),
					Intf: nil, // Intf is nil
				},
			},
			nexthopv4: systemops.Nexthop{
				IP:   netip.MustParseAddr("192.168.1.1"),
				Intf: &net.Interface{Index: 1, Name: "eth0"}, // Tracked Intf is not nil
			},
			expected: true,
		},
		{
			name: "added v4 route with same IP, different Intf Index should return true",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteAdded,
				Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP:   netip.MustParseAddr("192.168.1.1"),
					Intf: &net.Interface{Index: 2, Name: "eth1"}, // Different Intf Index
				},
			},
			nexthopv4: systemops.Nexthop{
				IP:   netip.MustParseAddr("192.168.1.1"),
				Intf: &net.Interface{Index: 1, Name: "eth0"},
			},
			expected: true,
		},
		{
			name: "deleted v4 route with same IP, different Intf Index should return false",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteDeleted,
				Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
				NextHop: systemops.Nexthop{ // This is the route being deleted
					IP:   netip.MustParseAddr("192.168.1.1"),
					Intf: &net.Interface{Index: 1, Name: "eth0"},
				},
			},
			nexthopv4: systemops.Nexthop{ // This is our tracked nexthop
				IP:   netip.MustParseAddr("192.168.1.1"),
				Intf: &net.Interface{Index: 2, Name: "eth1"}, // Different Intf Index
			},
			expected: false, // Because nexthopv4.Equal(route.NextHop) will be false
		},
		{
			name: "modified v6 route with different IP, same Intf Index should return true",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteModified,
				Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP:   netip.MustParseAddr("2001:db8::3"), // Different IP
					Intf: &net.Interface{Index: 1, Name: "eth0"},
				},
			},
			nexthopv6: systemops.Nexthop{
				IP:   netip.MustParseAddr("2001:db8::1"),
				Intf: &net.Interface{Index: 1, Name: "eth0"},
			},
			expected: true,
		},
		{
			name: "modified v6 route with same IP, different Intf Index should return true",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteModified,
				Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP:   netip.MustParseAddr("2001:db8::1"),
					Intf: &net.Interface{Index: 2, Name: "eth1"}, // Different Intf Index
				},
			},
			nexthopv6: systemops.Nexthop{
				IP:   netip.MustParseAddr("2001:db8::1"),
				Intf: &net.Interface{Index: 1, Name: "eth0"},
			},
			expected: true,
		},
		{
			name: "modified v6 route with same IP, same Intf Index should return false",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteModified,
				Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP:   netip.MustParseAddr("2001:db8::1"),
					Intf: &net.Interface{Index: 1, Name: "eth0"},
				},
			},
			nexthopv6: systemops.Nexthop{
				IP:   netip.MustParseAddr("2001:db8::1"),
				Intf: &net.Interface{Index: 1, Name: "eth0"},
			},
			expected: false,
		},
		{
			name: "deleted v6 route matching tracked nexthop (IP and Intf Index) should return true",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteDeleted,
				Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP:   netip.MustParseAddr("2001:db8::1"),
					Intf: &net.Interface{Index: 1, Name: "eth0"},
				},
			},
			nexthopv6: systemops.Nexthop{
				IP:   netip.MustParseAddr("2001:db8::1"),
				Intf: &net.Interface{Index: 1, Name: "eth0"},
			},
			expected: true,
		},
		{
			name: "deleted v6 route not matching tracked nexthop (different IP) should return false",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteDeleted,
				Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP:   netip.MustParseAddr("2001:db8::3"), // Different IP
					Intf: &net.Interface{Index: 1, Name: "eth0"},
				},
			},
			nexthopv6: systemops.Nexthop{
				IP:   netip.MustParseAddr("2001:db8::1"),
				Intf: &net.Interface{Index: 1, Name: "eth0"},
			},
			expected: false,
		},
		{
			name: "deleted v6 route not matching tracked nexthop (same IP, different Intf Index) should return false",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteDeleted,
				Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
				NextHop: systemops.Nexthop{ // This is the route being deleted
					IP:   netip.MustParseAddr("2001:db8::1"),
					Intf: &net.Interface{Index: 1, Name: "eth0"},
				},
			},
			nexthopv6: systemops.Nexthop{ // This is our tracked nexthop
				IP:   netip.MustParseAddr("2001:db8::1"),
				Intf: &net.Interface{Index: 2, Name: "eth1"}, // Different Intf Index
			},
			expected: false,
		},
		{
			name: "unknown route type should return false",
			route: systemops.RouteUpdate{
				Type:        systemops.RouteUpdateType(99), // Unknown type
				Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
				NextHop: systemops.Nexthop{
					IP:   netip.MustParseAddr("192.168.1.1"),
					Intf: &net.Interface{Index: 1, Name: "eth0"},
				},
			},
			nexthopv4: systemops.Nexthop{
				IP:   netip.MustParseAddr("192.168.1.2"), // Different from route.NextHop
				Intf: &net.Interface{Index: 1, Name: "eth0"},
			},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := routeChanged(tt.route, tt.nexthopv4, tt.nexthopv6)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestIsSoftInterface(t *testing.T) {
	tests := []struct {
		name     string
		ifname   string
		expected bool
	}{
		{
			name:     "ISATAP interface should be detected",
			ifname:   "ISATAP tunnel adapter",
			expected: true,
		},
		{
			name:     "lowercase soft interface should be detected",
			ifname:   "isatap.{14A5CF17-CA72-43EC-B4EA-B4B093641B7D}",
			expected: true,
		},
		{
			name:     "Teredo interface should be detected",
			ifname:   "Teredo Tunneling Pseudo-Interface",
			expected: true,
		},
		{
			name:     "regular interface should not be detected as soft",
			ifname:   "eth0",
			expected: false,
		},
		{
			name:     "another regular interface should not be detected as soft",
			ifname:   "wlan0",
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isSoftInterface(tt.ifname)
			assert.Equal(t, tt.expected, result)
		})
	}
}
