package cert_test

import (
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/lightningnetwork/lnd/cert"
	"github.com/stretchr/testify/require"
)

const (
	testTLSCertDuration = 42 * time.Hour
)

var (
	extraIPs     = []string{"1.1.1.1", "123.123.123.1", "199.189.12.12"}
	extraDomains = []string{"home", "and", "away"}
)

// TestIsOutdatedCert checks that we'll consider the TLS certificate outdated
// if the ip addresses or dns names don't match.
func TestIsOutdatedCert(t *testing.T) {
	tempDir := t.TempDir()

	certPath := tempDir + "/tls.cert"
	keyPath := tempDir + "/tls.key"

	// Generate TLS files with two extra IPs and domains.
	certBytes, keyBytes, err := cert.GenCertPair(
		"lnd autogenerated cert", extraIPs[:2], extraDomains[:2],
		false, testTLSCertDuration,
	)
	if err != nil {
		t.Fatal(err)
	}
	err = cert.WriteCertPair(certPath, keyPath, certBytes, keyBytes)
	require.NoError(t, err)

	// We'll attempt to check up-to-date status for all variants of 1-3
	// number of IPs and domains.
	for numIPs := 1; numIPs <= len(extraIPs); numIPs++ {
		for numDomains := 1; numDomains <= len(extraDomains); numDomains++ {
			certBytes, err := os.ReadFile(certPath)
			require.NoError(t, err)

			keyBytes, err := os.ReadFile(keyPath)
			require.NoError(t, err)

			_, parsedCert, err := cert.LoadCertFromBytes(
				certBytes, keyBytes,
			)
			if err != nil {
				t.Fatal(err)
			}

			// Using the test case's number of IPs and domains, get
			// the outdated status of the certificate we created
			// above.
			outdated, err := cert.IsOutdated(
				parsedCert, extraIPs[:numIPs],
				extraDomains[:numDomains], false,
			)
			if err != nil {
				t.Fatal(err)
			}

			// We expect it to be considered outdated if the IPs or
			// domains don't match exactly what we created.
			expected := numIPs != 2 || numDomains != 2
			if outdated != expected {
				t.Fatalf("expected certificate to be "+
					"outdated=%v, got=%v", expected,
					outdated)
			}
		}
	}
}

// TestIsOutdatedPermutation tests that the order of listed IPs or DNS names,
// nor duplicates in the lists, matter for whether we consider the certificate
// outdated.
func TestIsOutdatedPermutation(t *testing.T) {
	tempDir := t.TempDir()

	certPath := tempDir + "/tls.cert"
	keyPath := tempDir + "/tls.key"

	// Generate TLS files from the IPs and domains.
	certBytes, keyBytes, err := cert.GenCertPair(
		"lnd autogenerated cert", extraIPs[:], extraDomains[:],
		false, testTLSCertDuration,
	)
	if err != nil {
		t.Fatal(err)
	}
	err = cert.WriteCertPair(certPath, keyPath, certBytes, keyBytes)
	require.NoError(t, err)

	certBytes, err = os.ReadFile(certPath)
	require.NoError(t, err)

	keyBytes, err = os.ReadFile(keyPath)
	require.NoError(t, err)

	_, parsedCert, err := cert.LoadCertFromBytes(certBytes, keyBytes)
	require.NoError(t, err)

	// If we have duplicate IPs or DNS names listed, that shouldn't matter.
	dupIPs := make([]string, len(extraIPs)*2)
	for i := range dupIPs {
		dupIPs[i] = extraIPs[i/2]
	}

	dupDNS := make([]string, len(extraDomains)*2)
	for i := range dupDNS {
		dupDNS[i] = extraDomains[i/2]
	}

	outdated, err := cert.IsOutdated(parsedCert, dupIPs, dupDNS, false)
	if err != nil {
		t.Fatal(err)
	}

	if outdated {
		t.Fatalf("did not expect duplicate IPs or DNS names be " +
			"considered outdated")
	}

	// Similarly, the order of the lists shouldn't matter.
	revIPs := make([]string, len(extraIPs))
	for i := range revIPs {
		revIPs[i] = extraIPs[len(extraIPs)-1-i]
	}

	revDNS := make([]string, len(extraDomains))
	for i := range revDNS {
		revDNS[i] = extraDomains[len(extraDomains)-1-i]
	}

	outdated, err = cert.IsOutdated(parsedCert, revIPs, revDNS, false)
	if err != nil {
		t.Fatal(err)
	}

	if outdated {
		t.Fatalf("did not expect reversed IPs or DNS names be " +
			"considered outdated")
	}
}

// TestTLSDisableAutofill checks that setting the --tlsdisableautofill flag
// does not add interface ip addresses or hostnames to the cert.
func TestTLSDisableAutofill(t *testing.T) {
	tempDir := t.TempDir()

	certPath := tempDir + "/tls.cert"
	keyPath := tempDir + "/tls.key"

	// Generate TLS files with two extra IPs and domains and no interface IPs.
	certBytes, keyBytes, err := cert.GenCertPair(
		"lnd autogenerated cert", extraIPs[:2], extraDomains[:2],
		true, testTLSCertDuration,
	)
	require.NoError(
		t, err,
		"unable to generate tls certificate pair",
	)
	err = cert.WriteCertPair(certPath, keyPath, certBytes, keyBytes)
	require.NoError(t, err)

	// Read certs from disk.
	certBytes, err = os.ReadFile(certPath)
	require.NoError(t, err)

	keyBytes, err = os.ReadFile(keyPath)
	require.NoError(t, err)

	// Load the certificate.
	_, parsedCert, err := cert.LoadCertFromBytes(
		certBytes, keyBytes,
	)
	require.NoError(
		t, err,
		"unable to load tls certificate pair",
	)

	// Check if the TLS cert is outdated while still preventing
	// interface IPs from being used. Should not be outdated.
	shouldNotBeOutdated, err := cert.IsOutdated(
		parsedCert, extraIPs[:2],
		extraDomains[:2], true,
	)
	require.NoError(t, err)

	require.Equal(
		t, false, shouldNotBeOutdated,
		"TLS Certificate was marked as outdated when it should not be",
	)

	// Check if the TLS cert is outdated while allowing for
	// interface IPs to be used. Should report as outdated.
	shouldBeOutdated, err := cert.IsOutdated(
		parsedCert, extraIPs[:2],
		extraDomains[:2], false,
	)
	require.NoError(t, err)

	require.Equal(
		t, true, shouldBeOutdated,
		"TLS Certificate was not marked as outdated when it should be",
	)
}

// TestTLSConfig tests to ensure we can generate a TLS Config from
// a tls cert and tls key.
func TestTLSConfig(t *testing.T) {
	tempDir := t.TempDir()
	certPath := filepath.Join(tempDir, "/tls.cert")
	keyPath := filepath.Join(tempDir, "/tls.key")

	// Generate TLS files with an extra IP and domain.
	certBytes, keyBytes, err := cert.GenCertPair(
		"lnd autogenerated cert", []string{extraIPs[0]},
		[]string{extraDomains[0]}, false, testTLSCertDuration,
	)
	require.NoError(t, err)

	err = cert.WriteCertPair(certPath, keyPath, certBytes, keyBytes)
	require.NoError(t, err)

	certBytes, err = os.ReadFile(certPath)
	require.NoError(t, err)

	keyBytes, err = os.ReadFile(keyPath)
	require.NoError(t, err)

	// Load the certificate.
	certData, parsedCert, err := cert.LoadCertFromBytes(
		certBytes, keyBytes,
	)
	require.NoError(t, err)

	// Check to make sure the IP and domain are in the cert.
	var foundIp bool
	require.Contains(t, parsedCert.DNSNames, extraDomains[0])
	for _, ip := range parsedCert.IPAddresses {
		if ip.String() == extraIPs[0] {
			foundIp = true
			break
		}
	}
	require.Equal(t, true, foundIp, "Did not find required ip inside of "+
		"TLS Certificate.")

	// Create TLS Config.
	tlsCfg := cert.TLSConfFromCert(certData)

	require.Equal(t, 1, len(tlsCfg.Certificates))
}
