package files

import (
	"net/netip"
	"os"
	"path/filepath"
	"testing"

	"github.com/qdm12/gluetun/internal/configuration/settings"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/ini.v1"
)

func Test_Source_readWireguard(t *testing.T) {
	t.Parallel()

	t.Run("fail reading from file", func(t *testing.T) {
		t.Parallel()

		dirPath := t.TempDir()
		source := &Source{
			wireguardConfigPath: dirPath,
		}
		wireguard, err := source.readWireguard()
		assert.Equal(t, settings.Wireguard{}, wireguard)
		assert.Error(t, err)
		assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
	})

	t.Run("no file", func(t *testing.T) {
		t.Parallel()

		noFile := filepath.Join(t.TempDir(), "doesnotexist")
		source := &Source{
			wireguardConfigPath: noFile,
		}
		wireguard, err := source.readWireguard()
		assert.Equal(t, settings.Wireguard{}, wireguard)
		assert.NoError(t, err)
	})

	testCases := map[string]struct {
		fileContent string
		wireguard   settings.Wireguard
		errMessage  string
	}{
		"ini load error": {
			fileContent: "invalid",
			errMessage:  "loading ini from reader: key-value delimiter not found: invalid",
		},
		"empty file": {},
		"interface section parsing error": {
			fileContent: `
[Interface]
PrivateKey = x
`,
			errMessage: "parsing interface section: parsing PrivateKey: " +
				"x: wgtypes: failed to parse base64-encoded key: " +
				"illegal base64 data at input byte 0",
		},
		"success": {
			fileContent: `
[Interface]
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
Address = 10.38.22.35/32
DNS = 193.138.218.74

[Peer]
PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
`,
			wireguard: settings.Wireguard{
				PrivateKey:   ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
				PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
				Addresses: []netip.Prefix{
					netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
				},
			},
		},
	}

	for testName, testCase := range testCases {
		testCase := testCase
		t.Run(testName, func(t *testing.T) {
			t.Parallel()

			configFile := filepath.Join(t.TempDir(), "wg.conf")
			err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
			require.NoError(t, err)

			source := &Source{
				wireguardConfigPath: configFile,
			}

			wireguard, err := source.readWireguard()

			assert.Equal(t, testCase.wireguard, wireguard)
			if testCase.errMessage != "" {
				assert.EqualError(t, err, testCase.errMessage)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func Test_parseWireguardInterfaceSection(t *testing.T) {
	t.Parallel()

	testCases := map[string]struct {
		iniData    string
		wireguard  settings.Wireguard
		errMessage string
	}{
		"private key error": {
			iniData: `[Interface]
PrivateKey = x`,
			errMessage: "parsing PrivateKey: x: " +
				"wgtypes: failed to parse base64-encoded key: " +
				"illegal base64 data at input byte 0",
		},
		"address error": {
			iniData: `[Interface]
Address = x
`,
			errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP",
		},
		"success": {
			iniData: `
[Interface]
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
Address = 10.38.22.35/32
`,
			wireguard: settings.Wireguard{
				PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
				Addresses: []netip.Prefix{
					netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
				},
			},
		},
	}

	for testName, testCase := range testCases {
		testCase := testCase
		t.Run(testName, func(t *testing.T) {
			t.Parallel()

			iniFile, err := ini.Load([]byte(testCase.iniData))
			require.NoError(t, err)
			iniSection, err := iniFile.GetSection("Interface")
			require.NoError(t, err)

			var wireguard settings.Wireguard
			err = parseWireguardInterfaceSection(iniSection, &wireguard)

			assert.Equal(t, testCase.wireguard, wireguard)
			if testCase.errMessage != "" {
				assert.EqualError(t, err, testCase.errMessage)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func Test_parseINIWireguardKey(t *testing.T) {
	t.Parallel()

	testCases := map[string]struct {
		fileContent string
		keyName     string
		key         *string
		errMessage  string
	}{
		"key does not exist": {
			fileContent: `[Interface]`,
			keyName:     "PrivateKey",
		},
		"bad Wireguard key": {
			fileContent: `[Interface]
PrivateKey = x`,
			keyName: "PrivateKey",
			errMessage: "parsing PrivateKey: x: " +
				"wgtypes: failed to parse base64-encoded key: " +
				"illegal base64 data at input byte 0",
		},
		"success": {
			fileContent: `[Interface]
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
			keyName: "PrivateKey",
			key:     ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
		},
	}

	for testName, testCase := range testCases {
		testCase := testCase
		t.Run(testName, func(t *testing.T) {
			t.Parallel()

			iniFile, err := ini.Load([]byte(testCase.fileContent))
			require.NoError(t, err)
			iniSection, err := iniFile.GetSection("Interface")
			require.NoError(t, err)

			key, err := parseINIWireguardKey(iniSection, testCase.keyName)

			assert.Equal(t, testCase.key, key)
			if testCase.errMessage != "" {
				assert.EqualError(t, err, testCase.errMessage)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func Test_parseINIWireguardAddress(t *testing.T) {
	t.Parallel()

	testCases := map[string]struct {
		fileContent string
		addresses   []netip.Prefix
		errMessage  string
	}{
		"key does not exist": {
			fileContent: `[Interface]`,
		},
		"bad address": {
			fileContent: `[Interface]
Address = x`,
			errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP",
		},
		"success": {
			fileContent: `[Interface]
Address = 1.2.3.4/32, 5.6.7.8/32`,
			addresses: []netip.Prefix{
				netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 32),
				netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32),
			},
		},
	}

	for testName, testCase := range testCases {
		testCase := testCase
		t.Run(testName, func(t *testing.T) {
			t.Parallel()

			iniFile, err := ini.Load([]byte(testCase.fileContent))
			require.NoError(t, err)
			iniSection, err := iniFile.GetSection("Interface")
			require.NoError(t, err)

			addresses, err := parseINIWireguardAddress(iniSection)

			assert.Equal(t, testCase.addresses, addresses)
			if testCase.errMessage != "" {
				assert.EqualError(t, err, testCase.errMessage)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}
