// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package command

import (
	"crypto/x509"
	"os"
	"testing"
	"time"

	"github.com/hashicorp/nomad/testutil"
	"github.com/mitchellh/cli"
	"github.com/stretchr/testify/require"
)

func TestCACreateCommand(t *testing.T) {
	testDir := t.TempDir()
	previousDirectory, err := os.Getwd()
	require.NoError(t, err)
	require.NoError(t, os.Chdir(testDir))
	defer os.Chdir(previousDirectory)

	type testcase struct {
		name       string
		args       []string
		caPath     string
		keyPath    string
		extraCheck func(t *testing.T, cert *x509.Certificate)
	}
	// The following subtests must run serially.
	cases := []testcase{
		{"ca defaults",
			nil,
			"nomad-agent-ca.pem",
			"nomad-agent-ca-key.pem",
			func(t *testing.T, cert *x509.Certificate) {
				require.Equal(t, 1825*24*time.Hour, time.Until(cert.NotAfter).Round(24*time.Hour))
				require.False(t, cert.PermittedDNSDomainsCritical)
				require.Len(t, cert.PermittedDNSDomains, 0)
			},
		},
		{"ca options",
			[]string{
				"-days=365",
				"-name-constraint=true",
				"-domain=foo",
				"-additional-domain=bar",
				"-common-name=CustomCA",
				"-country=ZZ",
				"-organization=CustOrg",
				"-organizational-unit=CustOrgUnit",
			},
			"foo-agent-ca.pem",
			"foo-agent-ca-key.pem",
			func(t *testing.T, cert *x509.Certificate) {
				require.Equal(t, 365*24*time.Hour, time.Until(cert.NotAfter).Round(24*time.Hour))
				require.True(t, cert.PermittedDNSDomainsCritical)
				require.Len(t, cert.PermittedDNSDomains, 4)
				require.ElementsMatch(t, cert.PermittedDNSDomains, []string{"nomad", "foo", "localhost", "bar"})
				require.Equal(t, cert.Issuer.Organization, []string{"CustOrg"})
				require.Equal(t, cert.Issuer.OrganizationalUnit, []string{"CustOrgUnit"})
				require.Equal(t, cert.Issuer.Country, []string{"ZZ"})
				require.Contains(t, cert.Issuer.CommonName, "CustomCA")
			},
		},
		{"ca custom date",
			[]string{
				"-days=365",
			},
			"nomad-agent-ca.pem",
			"nomad-agent-ca-key.pem",
			func(t *testing.T, cert *x509.Certificate) {
				require.Equal(t, 365*24*time.Hour, time.Until(cert.NotAfter).Round(24*time.Hour))
			},
		},
	}
	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			ui := cli.NewMockUi()
			cmd := &TLSCACreateCommand{Meta: Meta{Ui: ui}}
			require.Equal(t, 0, cmd.Run(tc.args), ui.ErrorWriter.String())
			require.Equal(t, "", ui.ErrorWriter.String())
			// is a valid key
			key := testutil.IsValidSigner(t, tc.keyPath)
			require.True(t, key)
			// is a valid ca expects the ca
			ca := testutil.IsValidCertificate(t, tc.caPath)
			require.True(t, ca.BasicConstraintsValid)
			require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, ca.KeyUsage)
			require.True(t, ca.IsCA)
			require.Equal(t, ca.AuthorityKeyId, ca.SubjectKeyId)
			tc.extraCheck(t, ca)
			require.NoError(t, os.Remove(tc.caPath))
			require.NoError(t, os.Remove(tc.keyPath))
		})
	}
}
