//go:build linux

package aghnet

import (
	"net"
	"net/netip"
	"sync"
	"testing"
	"testing/fstest"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const arpAOutputWrt = `
IP address    HW type     Flags       HW address            Mask     Device
1.2.3.4.5     0x1         0x2         aa:bb:cc:dd:ee:ff     *        wan
1.2.3.4       0x1         0x2         12:34:56:78:910       *        wan
192.168.1.2   0x1         0x2         ab:cd:ef:ab:cd:ef     *        wan
::ffff:ffff   0x1         0x2         ef:cd:ab:ef:cd:ab     *        wan`

const arpAOutput = `
invalid.mac (1.2.3.4) at 12:34:56:78:910 on el0 ifscope [ethernet]
invalid.ip  (1.2.3.4.5) at ab:cd:ef:ab:cd:12 on ek0 ifscope [ethernet]
invalid.fmt 1 at 12:cd:ef:ab:cd:ef on er0 ifscope [ethernet]
? (192.168.1.2) at ab:cd:ef:ab:cd:ef on en0 ifscope [ethernet]
? (::ffff:ffff) at ef:cd:ab:ef:cd:ab on em0 expires in 100 seconds [ethernet]`

const ipNeighOutput = `
1.2.3.4.5 dev enp0s3 lladdr aa:bb:cc:dd:ee:ff DELAY
1.2.3.4 dev enp0s3 lladdr 12:34:56:78:910 DELAY
192.168.1.2 dev enp0s3 lladdr ab:cd:ef:ab:cd:ef DELAY
::ffff:ffff dev enp0s3 lladdr ef:cd:ab:ef:cd:ab router STALE`

var wantNeighs = []Neighbor{{
	IP:  netip.MustParseAddr("192.168.1.2"),
	MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
}, {
	IP:  netip.MustParseAddr("::ffff:ffff"),
	MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
}}

func TestFSysARPDB(t *testing.T) {
	require.NoError(t, fstest.TestFS(testdata, "proc_net_arp"))

	a := &fsysARPDB{
		ns: &neighs{
			mu: &sync.RWMutex{},
			ns: make([]Neighbor, 0),
		},
		fsys:     testdata,
		filename: "proc_net_arp",
	}

	err := a.Refresh()
	require.NoError(t, err)

	ns := a.Neighbors()
	assert.Equal(t, wantNeighs, ns)
}

func TestCmdARPDB_linux(t *testing.T) {
	sh := mapShell{
		"arp -a":   {err: nil, out: arpAOutputWrt, code: 0},
		"ip neigh": {err: nil, out: ipNeighOutput, code: 0},
	}
	substShell(t, sh.RunCmd)

	t.Run("wrt", func(t *testing.T) {
		a := &cmdARPDB{
			parse: parseArpAWrt,
			cmd:   "arp",
			args:  []string{"-a"},
			ns: &neighs{
				mu: &sync.RWMutex{},
				ns: make([]Neighbor, 0),
			},
		}

		err := a.Refresh()
		require.NoError(t, err)

		assert.Equal(t, wantNeighs, a.Neighbors())
	})

	t.Run("ip_neigh", func(t *testing.T) {
		a := &cmdARPDB{
			parse: parseIPNeigh,
			cmd:   "ip",
			args:  []string{"neigh"},
			ns: &neighs{
				mu: &sync.RWMutex{},
				ns: make([]Neighbor, 0),
			},
		}
		err := a.Refresh()
		require.NoError(t, err)

		assert.Equal(t, wantNeighs, a.Neighbors())
	})
}
