package dns01

import (
	"sort"
	"testing"

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

func TestLookupNameserversOK(t *testing.T) {
	testCases := []struct {
		fqdn string
		nss  []string
	}{
		{
			fqdn: "books.google.com.ng.",
			nss:  []string{"ns1.google.com.", "ns2.google.com.", "ns3.google.com.", "ns4.google.com."},
		},
		{
			fqdn: "www.google.com.",
			nss:  []string{"ns1.google.com.", "ns2.google.com.", "ns3.google.com.", "ns4.google.com."},
		},
		{
			fqdn: "physics.georgetown.edu.",
			nss:  []string{"ns4.georgetown.edu.", "ns5.georgetown.edu.", "ns6.georgetown.edu."},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.fqdn, func(t *testing.T) {
			t.Parallel()

			nss, err := lookupNameservers(test.fqdn)
			require.NoError(t, err)

			sort.Strings(nss)
			sort.Strings(test.nss)

			assert.EqualValues(t, test.nss, nss)
		})
	}
}

func TestLookupNameserversErr(t *testing.T) {
	testCases := []struct {
		desc  string
		fqdn  string
		error string
	}{
		{
			desc:  "invalid tld",
			fqdn:  "_null.n0n0.",
			error: "could not determine the zone",
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			_, err := lookupNameservers(test.fqdn)
			require.Error(t, err)
			assert.Contains(t, err.Error(), test.error)
		})
	}
}

func TestFindZoneByFqdnCustom(t *testing.T) {
	testCases := []struct {
		desc          string
		fqdn          string
		zone          string
		nameservers   []string
		expectedError string
	}{
		{
			desc:        "domain is a CNAME",
			fqdn:        "mail.google.com.",
			zone:        "google.com.",
			nameservers: recursiveNameservers,
		},
		{
			desc:        "domain is a non-existent subdomain",
			fqdn:        "foo.google.com.",
			zone:        "google.com.",
			nameservers: recursiveNameservers,
		},
		{
			desc:        "domain is a eTLD",
			fqdn:        "example.com.ac.",
			zone:        "ac.",
			nameservers: recursiveNameservers,
		},
		{
			desc:        "domain is a cross-zone CNAME",
			fqdn:        "cross-zone-example.assets.sh.",
			zone:        "assets.sh.",
			nameservers: recursiveNameservers,
		},
		{
			desc:          "NXDOMAIN",
			fqdn:          "test.loho.jkl.",
			zone:          "loho.jkl.",
			nameservers:   []string{"1.1.1.1:53"},
			expectedError: "could not find the start of authority for test.loho.jkl.: NXDOMAIN",
		},
		{
			desc:        "several non existent nameservers",
			fqdn:        "mail.google.com.",
			zone:        "google.com.",
			nameservers: []string{":7053", ":8053", "1.1.1.1:53"},
		},
		{
			desc:          "only non existent nameservers",
			fqdn:          "mail.google.com.",
			zone:          "google.com.",
			nameservers:   []string{":7053", ":8053", ":9053"},
			expectedError: "could not find the start of authority for mail.google.com.: read udp",
		},
		{
			desc:          "no nameservers",
			fqdn:          "test.ldez.com.",
			zone:          "ldez.com.",
			nameservers:   []string{},
			expectedError: "could not find the start of authority for test.ldez.com.",
		},
	}

	for _, test := range testCases {
		t.Run(test.desc, func(t *testing.T) {
			ClearFqdnCache()

			zone, err := FindZoneByFqdnCustom(test.fqdn, test.nameservers)
			if test.expectedError != "" {
				require.Error(t, err)
				assert.Contains(t, err.Error(), test.expectedError)
			} else {
				require.NoError(t, err)
				assert.Equal(t, test.zone, zone)
			}
		})
	}
}

func TestResolveConfServers(t *testing.T) {
	var testCases = []struct {
		fixture  string
		expected []string
		defaults []string
	}{
		{
			fixture:  "fixtures/resolv.conf.1",
			defaults: []string{"127.0.0.1:53"},
			expected: []string{"10.200.3.249:53", "10.200.3.250:5353", "[2001:4860:4860::8844]:53", "[10.0.0.1]:5353"},
		},
		{
			fixture:  "fixtures/resolv.conf.nonexistant",
			defaults: []string{"127.0.0.1:53"},
			expected: []string{"127.0.0.1:53"},
		},
	}

	for _, test := range testCases {
		t.Run(test.fixture, func(t *testing.T) {

			result := getNameservers(test.fixture, test.defaults)

			sort.Strings(result)
			sort.Strings(test.expected)

			assert.Equal(t, test.expected, result)
		})
	}
}
