// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package dynamiccertauthority

import (
	"crypto/x509/pkix"
	"io/ioutil"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"go.pinniped.dev/internal/dynamiccert"
	"go.pinniped.dev/internal/testutil"
)

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

	provider := dynamiccert.New()
	ca := New(provider)

	steps := []struct {
		name                 string
		caCrtPath, caKeyPath string
		wantError            string
	}{
		{
			name:      "no cert+key",
			wantError: "could not load CA: tls: failed to find any PEM data in certificate input",
		},
		{
			name:      "only cert",
			caCrtPath: "testdata/ca-0.crt",
			wantError: "could not load CA: tls: failed to find any PEM data in key input",
		},
		{
			name:      "only key",
			caKeyPath: "testdata/ca-0.key",
			wantError: "could not load CA: tls: failed to find any PEM data in certificate input",
		},
		{
			name:      "new cert+key",
			caCrtPath: "testdata/ca-0.crt",
			caKeyPath: "testdata/ca-0.key",
		},
		{
			name: "same cert+key",
		},
		{
			name:      "another new cert+key",
			caCrtPath: "testdata/ca-1.crt",
			caKeyPath: "testdata/ca-1.key",
		},
		{
			name:      "bad cert",
			caCrtPath: "testdata/ca-bad.crt",
			caKeyPath: "testdata/ca-0.key",
			wantError: "could not load CA: tls: failed to find any PEM data in certificate input",
		},
		{
			name:      "bad key",
			caCrtPath: "testdata/ca-0.crt",
			caKeyPath: "testdata/ca-bad.key",
			wantError: "could not load CA: tls: failed to find any PEM data in key input",
		},
		{
			name:      "mismatch cert+key",
			caCrtPath: "testdata/ca-0.crt",
			caKeyPath: "testdata/ca-1.key",
			wantError: "could not load CA: tls: private key does not match public key",
		},
		{
			name:      "good cert+key again",
			caCrtPath: "testdata/ca-1.crt",
			caKeyPath: "testdata/ca-1.key",
		},
	}
	for _, step := range steps {
		step := step
		t.Run(step.name, func(t *testing.T) {
			var caCrtPEM, caKeyPEM []byte
			var err error
			if step.caCrtPath != "" {
				caCrtPEM, err = ioutil.ReadFile(step.caCrtPath)
				require.NoError(t, err)
			}

			if step.caKeyPath != "" {
				caKeyPEM, err = ioutil.ReadFile(step.caKeyPath)
				require.NoError(t, err)
			}

			if step.caCrtPath != "" || step.caKeyPath != "" {
				provider.Set(caCrtPEM, caKeyPEM)
			} else {
				caCrtPEM, _ = provider.CurrentCertKeyContent()
			}

			crtPEM, keyPEM, err := ca.IssuePEM(
				pkix.Name{
					CommonName: "some-common-name",
				},
				[]string{"some-dns-name", "some-other-dns-name"},
				time.Hour*24,
			)

			if step.wantError != "" {
				require.EqualError(t, err, step.wantError)
				require.Empty(t, crtPEM)
				require.Empty(t, keyPEM)
			} else {
				require.NoError(t, err)
				require.NotEmpty(t, crtPEM)
				require.NotEmpty(t, keyPEM)

				crtAssertions := testutil.ValidateCertificate(t, string(caCrtPEM), string(crtPEM))
				crtAssertions.RequireCommonName("some-common-name")
				crtAssertions.RequireDNSName("some-dns-name")
				crtAssertions.RequireDNSName("some-other-dns-name")
				crtAssertions.RequireLifetime(time.Now(), time.Now().Add(time.Hour*24), time.Minute*10)
				crtAssertions.RequireMatchesPrivateKey(string(keyPEM))
			}
		})
	}
}