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

package command

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/hashicorp/nomad/ci"
	"github.com/mitchellh/cli"
	"github.com/shoenig/test"
	"github.com/shoenig/test/must"
)

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

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

	// Start test server.
	srv, client, url := testServer(t, true, nil)
	defer srv.Shutdown()

	waitForNodes(t, client)

	// Initialize UI and command.
	ui := cli.NewMockUi()
	cmd := &NodePoolApplyCommand{Meta: Meta{Ui: ui}}

	// Create node pool with HCL file.
	hclTestFile := `
node_pool "dev" {
  description = "dev node pool"
}`
	file, err := os.CreateTemp(t.TempDir(), "node-pool-test-*.hcl")
	must.NoError(t, err)
	_, err = file.WriteString(hclTestFile)
	must.NoError(t, err)

	// Run command.
	args := []string{"-address", url, file.Name()}
	code := cmd.Run(args)
	must.Eq(t, 0, code)

	// Verify node pool was created.
	got, err := srv.Agent.Server().State().NodePoolByName(nil, "dev")
	must.NoError(t, err)
	must.NotNil(t, got)

	// Update node pool.
	file.Truncate(0)
	file.Seek(0, 0)
	hclTestFile = `
node_pool "dev" {
  description = "dev node pool"

  meta {
    test = "true"
  }
}`
	_, err = file.WriteString(hclTestFile)
	must.NoError(t, err)

	// Run command.
	code = cmd.Run(args)
	must.Eq(t, 0, code)

	// Verify node pool was updated.
	got, err = srv.Agent.Server().State().NodePoolByName(nil, "dev")
	must.NoError(t, err)
	must.NotNil(t, got)
	must.NotNil(t, got.Meta)
	must.Eq(t, "true", got.Meta["test"])

	// Create node pool with JSON file.
	jsonTestFile := `
{
  "Name": "prod",
  "Description": "prod node pool"
}`

	file, err = os.CreateTemp(t.TempDir(), "node-pool-test-*.json")
	must.NoError(t, err)
	_, err = file.WriteString(jsonTestFile)
	must.NoError(t, err)

	// Run command.
	args = []string{"-address", url, "-json", file.Name()}
	code = cmd.Run(args)
	must.Eq(t, 0, code)

	// Verify node pool was created.
	got, err = srv.Agent.Server().State().NodePoolByName(nil, "prod")
	must.NoError(t, err)
	must.NotNil(t, got)
}

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

	// Start test server.
	srv, client, url := testServer(t, true, nil)
	defer srv.Shutdown()

	waitForNodes(t, client)

	testCases := []struct {
		name           string
		args           []string
		input          string
		expectedOutput string
		expectedCode   int
	}{
		{
			name:           "missing file",
			args:           []string{},
			expectedOutput: "This command takes one argument",
			expectedCode:   1,
		},
		{
			name:           "file doesn't exist",
			args:           []string{"doesn-exist.hcl"},
			expectedOutput: "no such file",
			expectedCode:   1,
		},
		{
			name:           "invalid json",
			args:           []string{"-json", "invalid.json"},
			input:          "not json",
			expectedOutput: "Failed to parse input",
			expectedCode:   1,
		},
		{
			name:           "invalid hcl",
			args:           []string{"invalid.hcl"},
			input:          "not HCL",
			expectedOutput: "Failed to parse input",
			expectedCode:   1,
		},
		{
			name:           "valid json without json flag",
			args:           []string{"valid.json"},
			input:          `{"Name": "dev"}`,
			expectedOutput: "Failed to parse input",
			expectedCode:   1,
		},
		{
			name:           "valid hcl with json flag",
			args:           []string{"-json", "valid.hcl"},
			input:          `node_pool "dev" {}`,
			expectedOutput: "Failed to parse input",
			expectedCode:   1,
		},
		{
			name:           "invalid node pool hcl",
			args:           []string{"invalid.hcl"},
			input:          `not_a_node_pool "dev" {}`,
			expectedOutput: "Failed to parse input",
			expectedCode:   1,
		},
		{
			name:           "invalid node pool",
			args:           []string{"-address", url, "invalid_node_pool.hcl"},
			input:          `node_pool "invalid name" {}`,
			expectedOutput: "Error applying node pool",
			expectedCode:   1,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Initialize UI and command.
			ui := cli.NewMockUi()
			cmd := &NodePoolApplyCommand{Meta: Meta{Ui: ui}}

			// Write input to file.
			if tc.input != "" {
				// Split the filename and extension from the last argument to
				// add a "*" between them so os.CreateTemp retains the file
				// extension.
				filename := tc.args[len(tc.args)-1]
				ext := filepath.Ext(filename)
				name, _ := strings.CutSuffix(filename, ext)

				file, err := os.CreateTemp(t.TempDir(), fmt.Sprintf("%s-*%s", name, ext))
				must.NoError(t, err)
				_, err = file.WriteString(tc.input)
				must.NoError(t, err)

				// Update last arg with full test file path.
				tc.args[len(tc.args)-1] = file.Name()
			}

			got := cmd.Run(tc.args)
			test.Eq(t, tc.expectedCode, got)
			test.StrContains(t, ui.ErrorWriter.String(), tc.expectedOutput)
		})
	}
}
