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

package command

import (
	"encoding/json"
	"sort"
	"strings"
	"testing"

	"github.com/hashicorp/nomad/api"
	"github.com/hashicorp/nomad/ci"
	"github.com/hashicorp/nomad/command/agent"
	"github.com/hashicorp/nomad/helper"
	"github.com/hashicorp/nomad/testutil"
	"github.com/mitchellh/cli"
	"github.com/shoenig/test/must"
)

func TestNodePoolNodesCommand_Implements(t *testing.T) {
	ci.Parallel(t)
	var _ cli.Command = &NodePoolNodesCommand{}
}

func TestNodePoolNodesCommand_Run(t *testing.T) {
	ci.Parallel(t)

	// Start test server.
	srv, client, url := testServer(t, true, func(c *agent.Config) {
		c.Client.Enabled = false
	})
	testutil.WaitForLeader(t, srv.Agent.RPC)

	// Start some test clients.
	rpcAddr := srv.GetConfig().AdvertiseAddrs.RPC
	clientNodePoolConfig := func(pool string) func(*agent.Config) {
		return func(c *agent.Config) {
			c.Client.NodePool = pool
			c.Client.Servers = []string{rpcAddr}
			c.Client.Enabled = true
			c.Server.Enabled = false
		}
	}

	testClient(t, "client-default", clientNodePoolConfig(""))
	testClient(t, "client-dev", clientNodePoolConfig("dev"))
	testClient(t, "client-prod-1", clientNodePoolConfig("prod"))
	testClient(t, "client-prod-2", clientNodePoolConfig("prod"))
	waitForNodes(t, client)

	nodes, _, err := client.Nodes().List(nil)
	must.NoError(t, err)

	// Nodes().List() sort results by CreateIndex, but for pagination we need
	// nodes sorted by ID.
	sort.Slice(nodes, func(i, j int) bool {
		return nodes[i].ID < nodes[j].ID
	})

	testCases := []struct {
		name          string
		args          []string
		expectedCode  int
		expectedNodes []string
		expectedErr   string
	}{
		{
			name:         "nodes in prod",
			args:         []string{"prod"},
			expectedCode: 0,
			expectedNodes: []string{
				"client-prod-1",
				"client-prod-2",
			},
		},
		{
			name:         "nodes in all",
			args:         []string{"all"},
			expectedCode: 0,
			expectedNodes: []string{
				"client-default",
				"client-dev",
				"client-prod-1",
				"client-prod-2",
			},
		},
		{
			name:         "filter nodes",
			args:         []string{"-filter", `Name matches "dev"`, "all"},
			expectedCode: 0,
			expectedNodes: []string{
				"client-dev",
			},
		},
		{
			name: "pool by prefix",
			args: []string{"def"},
			expectedNodes: []string{
				"client-default",
			},
		},
		{
			name:         "paginate page 1",
			args:         []string{"-per-page=2", "all"},
			expectedCode: 0,
			expectedNodes: []string{
				nodes[0].Name,
				nodes[1].Name,
			},
		},
		{
			name:         "paginate page 2",
			args:         []string{"-per-page", "2", "-page-token", nodes[2].ID, "all"},
			expectedCode: 0,
			expectedNodes: []string{
				nodes[2].Name,
				nodes[3].Name,
			},
		},
		{
			name:         "missing pool name",
			args:         []string{},
			expectedCode: 1,
			expectedErr:  "This command takes one argument",
		},
		{
			name:         "prefix match multiple",
			args:         []string{"de"},
			expectedCode: 1,
			expectedErr:  "Prefix matched multiple node pools",
		},
		{
			name:         "json and template not allowed",
			args:         []string{"-t", "{{.}}", "all"},
			expectedCode: 1,
			expectedErr:  "Both json and template formatting are not allowed",
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Initialize UI and command.
			ui := cli.NewMockUi()
			cmd := &NodePoolNodesCommand{Meta: Meta{Ui: ui}}

			// Run command.
			// Add -json to help parse and validate results.
			args := []string{"-address", url, "-json"}
			args = append(args, tc.args...)
			code := cmd.Run(args)

			if tc.expectedErr != "" {
				must.StrContains(t, ui.ErrorWriter.String(), strings.TrimSpace(tc.expectedErr))
			} else {
				must.Eq(t, "", ui.ErrorWriter.String())

				var nodes []*api.NodeListStub
				err := json.Unmarshal(ui.OutputWriter.Bytes(), &nodes)
				must.NoError(t, err)

				gotNodes := helper.ConvertSlice(nodes,
					func(n *api.NodeListStub) string { return n.Name })
				must.SliceContainsAll(t, tc.expectedNodes, gotNodes)
			}
			must.Eq(t, tc.expectedCode, code)
		})
	}

	t.Run("template formatting", func(t *testing.T) {
		// Initialize UI and command.
		ui := cli.NewMockUi()
		cmd := &NodePoolNodesCommand{Meta: Meta{Ui: ui}}

		// Run command.
		args := []string{"-address", url, "-t", `{{range .}}{{.ID}} {{end}}`, "all"}
		code := cmd.Run(args)
		must.Zero(t, code)

		var expected string
		for _, n := range nodes {
			expected += n.ID + " "
		}
		got := ui.OutputWriter.String()

		must.Eq(t, strings.TrimSpace(expected), strings.TrimSpace(got))
	})
}
