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

package agent

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/hashicorp/consul/sdk/testutil"
	"github.com/hashicorp/memberlist"
	"github.com/stretchr/testify/require"
)

func checkForKey(key string, keyring *memberlist.Keyring) error {
	rk, err := base64.StdEncoding.DecodeString(key)
	if err != nil {
		return err
	}

	pk := keyring.GetPrimaryKey()
	if !bytes.Equal(rk, pk) {
		return fmt.Errorf("got %q want %q", pk, rk)
	}
	return nil
}

func TestAgent_LoadKeyrings(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	key := "tbLJg26ZJyJ9pK3qhc9jig=="

	// Should be no configured keyring file by default
	t.Run("no keys", func(t *testing.T) {
		a1 := NewTestAgent(t, "")
		defer a1.Shutdown()

		c1 := a1.consulConfig()
		if c1.SerfLANConfig.KeyringFile != "" {
			t.Fatalf("bad: %#v", c1.SerfLANConfig.KeyringFile)
		}
		if c1.SerfLANConfig.MemberlistConfig.Keyring != nil {
			t.Fatalf("keyring should not be loaded")
		}
		if c1.SerfWANConfig.KeyringFile != "" {
			t.Fatalf("bad: %#v", c1.SerfLANConfig.KeyringFile)
		}
		if c1.SerfWANConfig.MemberlistConfig.Keyring != nil {
			t.Fatalf("keyring should not be loaded")
		}
	})

	// Server should auto-load LAN and WAN keyring files
	t.Run("server with keys", func(t *testing.T) {
		dataDir := testutil.TempDir(t, "keyfile")
		writeKeyRings(t, key, dataDir)

		a2 := StartTestAgent(t, TestAgent{DataDir: dataDir})
		defer a2.Shutdown()

		c2 := a2.consulConfig()
		if c2.SerfLANConfig.KeyringFile == "" {
			t.Fatalf("should have keyring file")
		}
		if c2.SerfLANConfig.MemberlistConfig.Keyring == nil {
			t.Fatalf("keyring should be loaded")
		}
		if err := checkForKey(key, c2.SerfLANConfig.MemberlistConfig.Keyring); err != nil {
			t.Fatalf("err: %v", err)
		}
		if c2.SerfWANConfig.KeyringFile == "" {
			t.Fatalf("should have keyring file")
		}
		if c2.SerfWANConfig.MemberlistConfig.Keyring == nil {
			t.Fatalf("keyring should be loaded")
		}
		if err := checkForKey(key, c2.SerfWANConfig.MemberlistConfig.Keyring); err != nil {
			t.Fatalf("err: %v", err)
		}
	})

	// Client should auto-load only the LAN keyring file
	t.Run("client with keys", func(t *testing.T) {
		dataDir := testutil.TempDir(t, "keyfile")
		writeKeyRings(t, key, dataDir)

		a3 := StartTestAgent(t, TestAgent{
			HCL: `
			server = false
			bootstrap = false
			`,
			DataDir: dataDir,
		})
		defer a3.Shutdown()

		c3 := a3.consulConfig()
		if c3.SerfLANConfig.KeyringFile == "" {
			t.Fatalf("should have keyring file")
		}
		if c3.SerfLANConfig.MemberlistConfig.Keyring == nil {
			t.Fatalf("keyring should be loaded")
		}
		if err := checkForKey(key, c3.SerfLANConfig.MemberlistConfig.Keyring); err != nil {
			t.Fatalf("err: %v", err)
		}
		if c3.SerfWANConfig.KeyringFile != "" {
			t.Fatalf("bad: %#v", c3.SerfWANConfig.KeyringFile)
		}
		if c3.SerfWANConfig.MemberlistConfig.Keyring != nil {
			t.Fatalf("keyring should not be loaded")
		}
	})
}

func writeKeyRings(t *testing.T, key string, dataDir string) {
	t.Helper()
	writeKey := func(key, filename string) {
		path := filepath.Join(dataDir, filename)
		require.NoError(t, initKeyring(path, key), "Error creating keyring %s", path)
	}
	writeKey(key, SerfLANKeyring)
	writeKey(key, SerfWANKeyring)
}

func TestAgent_InmemKeyrings(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	key := "tbLJg26ZJyJ9pK3qhc9jig=="

	// Should be no configured keyring file by default
	t.Run("no keys", func(t *testing.T) {
		a1 := NewTestAgent(t, "")
		defer a1.Shutdown()

		c1 := a1.consulConfig()
		if c1.SerfLANConfig.KeyringFile != "" {
			t.Fatalf("bad: %#v", c1.SerfLANConfig.KeyringFile)
		}
		if c1.SerfLANConfig.MemberlistConfig.Keyring != nil {
			t.Fatalf("keyring should not be loaded")
		}
		if c1.SerfWANConfig.KeyringFile != "" {
			t.Fatalf("bad: %#v", c1.SerfLANConfig.KeyringFile)
		}
		if c1.SerfWANConfig.MemberlistConfig.Keyring != nil {
			t.Fatalf("keyring should not be loaded")
		}
	})

	// Server should auto-load LAN and WAN keyring
	t.Run("server with keys", func(t *testing.T) {
		a2 := NewTestAgent(t, `
			encrypt = "`+key+`"
			disable_keyring_file = true
		`)
		defer a2.Shutdown()

		c2 := a2.consulConfig()
		if c2.SerfLANConfig.KeyringFile != "" {
			t.Fatalf("should not have keyring file")
		}
		if c2.SerfLANConfig.MemberlistConfig.Keyring == nil {
			t.Fatalf("keyring should be loaded")
		}
		if err := checkForKey(key, c2.SerfLANConfig.MemberlistConfig.Keyring); err != nil {
			t.Fatalf("err: %v", err)
		}
		if c2.SerfWANConfig.KeyringFile != "" {
			t.Fatalf("should not have keyring file")
		}
		if c2.SerfWANConfig.MemberlistConfig.Keyring == nil {
			t.Fatalf("keyring should be loaded")
		}
		if err := checkForKey(key, c2.SerfWANConfig.MemberlistConfig.Keyring); err != nil {
			t.Fatalf("err: %v", err)
		}
	})

	// Client should auto-load only the LAN keyring
	t.Run("client with keys", func(t *testing.T) {
		a3 := NewTestAgent(t, `
			encrypt = "`+key+`"
			server = false
			bootstrap = false
			disable_keyring_file = true
		`)
		defer a3.Shutdown()

		c3 := a3.consulConfig()
		if c3.SerfLANConfig.KeyringFile != "" {
			t.Fatalf("should not have keyring file")
		}
		if c3.SerfLANConfig.MemberlistConfig.Keyring == nil {
			t.Fatalf("keyring should be loaded")
		}
		if err := checkForKey(key, c3.SerfLANConfig.MemberlistConfig.Keyring); err != nil {
			t.Fatalf("err: %v", err)
		}
		if c3.SerfWANConfig.KeyringFile != "" {
			t.Fatalf("bad: %#v", c3.SerfWANConfig.KeyringFile)
		}
		if c3.SerfWANConfig.MemberlistConfig.Keyring != nil {
			t.Fatalf("keyring should not be loaded")
		}
	})

	// Any keyring files should be ignored
	t.Run("ignore files", func(t *testing.T) {
		dir := testutil.TempDir(t, "consul")

		badKey := "unUzC2X3JgMKVJlZna5KVg=="
		if err := initKeyring(filepath.Join(dir, SerfLANKeyring), badKey); err != nil {
			t.Fatalf("err: %v", err)
		}
		if err := initKeyring(filepath.Join(dir, SerfWANKeyring), badKey); err != nil {
			t.Fatalf("err: %v", err)
		}

		a4 := NewTestAgent(t, `
			encrypt = "`+key+`"
			disable_keyring_file = true
			data_dir = "`+dir+`"
		`)
		defer a4.Shutdown()

		c4 := a4.consulConfig()
		if c4.SerfLANConfig.KeyringFile != "" {
			t.Fatalf("should not have keyring file")
		}
		if c4.SerfLANConfig.MemberlistConfig.Keyring == nil {
			t.Fatalf("keyring should be loaded")
		}
		if err := checkForKey(key, c4.SerfLANConfig.MemberlistConfig.Keyring); err != nil {
			t.Fatalf("err: %v", err)
		}
		if c4.SerfWANConfig.KeyringFile != "" {
			t.Fatalf("should not have keyring file")
		}
		if c4.SerfWANConfig.MemberlistConfig.Keyring == nil {
			t.Fatalf("keyring should be loaded")
		}
		if err := checkForKey(key, c4.SerfWANConfig.MemberlistConfig.Keyring); err != nil {
			t.Fatalf("err: %v", err)
		}
	})
}

func TestAgent_InitKeyring(t *testing.T) {
	t.Parallel()
	key1 := "tbLJg26ZJyJ9pK3qhc9jig=="
	key2 := "4leC33rgtXKIVUr9Nr0snQ=="
	expected := fmt.Sprintf(`["%s"]`, key1)

	dir := testutil.TempDir(t, "consul")
	file := filepath.Join(dir, "keyring")

	// First initialize the keyring
	if err := initKeyring(file, key1); err != nil {
		t.Fatalf("err: %s", err)
	}

	content, err := os.ReadFile(file)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if string(content) != expected {
		t.Fatalf("bad: %s", content)
	}

	// Try initializing again with a different key
	if err := initKeyring(file, key2); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Content should still be the same
	content, err = os.ReadFile(file)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if string(content) != expected {
		t.Fatalf("bad: %s", content)
	}
}

func TestAgentKeyring_ACL(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	key1 := "tbLJg26ZJyJ9pK3qhc9jig=="
	key2 := "4leC33rgtXKIVUr9Nr0snQ=="

	dataDir := testutil.TempDir(t, "keyfile")
	writeKeyRings(t, key1, dataDir)

	a := StartTestAgent(t, TestAgent{HCL: `
		primary_datacenter = "dc1"

		acl {
			enabled = true
			default_policy = "deny"

			tokens {
				initial_management = "root"
			}
		}
	`, DataDir: dataDir})
	defer a.Shutdown()

	// List keys without access fails
	_, err := a.ListKeys("", false, 0)
	if err == nil || !strings.Contains(err.Error(), "denied") {
		t.Fatalf("expected denied error, got: %#v", err)
	}

	// List keys with access works
	_, err = a.ListKeys("root", false, 0)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Install without access fails
	_, err = a.InstallKey(key2, "", 0)
	if err == nil || !strings.Contains(err.Error(), "denied") {
		t.Fatalf("expected denied error, got: %#v", err)
	}

	// Install with access works
	_, err = a.InstallKey(key2, "root", 0)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Use without access fails
	_, err = a.UseKey(key2, "", 0)
	if err == nil || !strings.Contains(err.Error(), "denied") {
		t.Fatalf("expected denied error, got: %#v", err)
	}

	// Use with access works
	_, err = a.UseKey(key2, "root", 0)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Remove without access fails
	_, err = a.RemoveKey(key1, "", 0)
	if err == nil || !strings.Contains(err.Error(), "denied") {
		t.Fatalf("expected denied error, got: %#v", err)
	}

	// Remove with access works
	_, err = a.RemoveKey(key1, "root", 0)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestValidateLocalOnly(t *testing.T) {
	require.NoError(t, ValidateLocalOnly(false, false))
	require.NoError(t, ValidateLocalOnly(true, true))

	require.Error(t, ValidateLocalOnly(true, false))
}

func TestAgent_KeyringIsMissingKey(t *testing.T) {
	key1 := "tbLJg26ZJyJ9pK3qhc9jig=="
	key2 := "4leC33rgtXKIVUr9Nr0snQ=="
	decoded1, err := decodeStringKey(key1)
	require.NoError(t, err)
	keyring, err := memberlist.NewKeyring([][]byte{}, decoded1)
	require.NoError(t, err)

	require.True(t, keyringIsMissingKey(keyring, key2))
	require.False(t, keyringIsMissingKey(keyring, key1))
}
