package cli_test

import (
	"bytes"
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/coder/coder/v2/cli/clitest"
	"github.com/coder/coder/v2/coderd/coderdtest"
	"github.com/coder/coder/v2/coderd/database"
	"github.com/coder/coder/v2/provisioner/echo"
	"github.com/coder/coder/v2/provisionersdk/proto"
	"github.com/coder/coder/v2/pty/ptytest"
	"github.com/coder/coder/v2/testutil"
)

func TestTemplateCreate(t *testing.T) {
	t.Parallel()
	t.Run("Create", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		coderdtest.CreateFirstUser(t, client)
		source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
		args := []string{
			"templates",
			"create",
			"my-template",
			"--directory", source,
			"--test.provisioner", string(database.ProvisionerTypeEcho),
			"--default-ttl", "24h",
		}
		inv, root := clitest.New(t, args...)
		clitest.SetupConfig(t, client, root)
		pty := ptytest.New(t).Attach(inv)

		clitest.Start(t, inv)

		matches := []struct {
			match string
			write string
		}{
			{match: "Upload", write: "yes"},
			{match: "compute.main"},
			{match: "smith (linux, i386)"},
			{match: "Confirm create?", write: "yes"},
		}
		for _, m := range matches {
			pty.ExpectMatch(m.match)
			if len(m.write) > 0 {
				pty.WriteLine(m.write)
			}
		}
	})
	t.Run("CreateNoLockfile", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		coderdtest.CreateFirstUser(t, client)
		source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
		require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl")))
		args := []string{
			"templates",
			"create",
			"my-template",
			"--directory", source,
			"--test.provisioner", string(database.ProvisionerTypeEcho),
			"--default-ttl", "24h",
		}
		inv, root := clitest.New(t, args...)
		clitest.SetupConfig(t, client, root)
		pty := ptytest.New(t).Attach(inv)

		execDone := make(chan error)
		go func() {
			execDone <- inv.Run()
		}()

		matches := []struct {
			match string
			write string
		}{
			{match: "No .terraform.lock.hcl file found"},
			{match: "Upload", write: "no"},
		}
		for _, m := range matches {
			pty.ExpectMatch(m.match)
			if len(m.write) > 0 {
				pty.WriteLine(m.write)
			}
		}

		// cmd should error once we say no.
		require.Error(t, <-execDone)
	})
	t.Run("CreateNoLockfileIgnored", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		coderdtest.CreateFirstUser(t, client)
		source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
		require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl")))
		args := []string{
			"templates",
			"create",
			"my-template",
			"--directory", source,
			"--test.provisioner", string(database.ProvisionerTypeEcho),
			"--default-ttl", "24h",
			"--ignore-lockfile",
		}
		inv, root := clitest.New(t, args...)
		clitest.SetupConfig(t, client, root)
		pty := ptytest.New(t).Attach(inv)

		execDone := make(chan error)
		go func() {
			execDone <- inv.Run()
		}()

		{
			ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
			defer cancel()

			pty.ExpectNoMatchBefore(ctx, "No .terraform.lock.hcl file found", "Upload")
			pty.WriteLine("no")
		}

		// cmd should error once we say no.
		require.Error(t, <-execDone)
	})

	t.Run("CreateStdin", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		coderdtest.CreateFirstUser(t, client)
		source, err := echo.Tar(completeWithAgent())
		require.NoError(t, err)

		args := []string{
			"templates",
			"create",
			"my-template",
			"--directory", "-",
			"--test.provisioner", string(database.ProvisionerTypeEcho),
			"--default-ttl", "24h",
		}
		inv, root := clitest.New(t, args...)
		clitest.SetupConfig(t, client, root)
		pty := ptytest.New(t)
		inv.Stdin = bytes.NewReader(source)
		inv.Stdout = pty.Output()

		require.NoError(t, inv.Run())
	})

	t.Run("Recreate template with same name (create, delete, create)", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		coderdtest.CreateFirstUser(t, client)

		create := func() error {
			source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
			args := []string{
				"templates",
				"create",
				"my-template",
				"--yes",
				"--directory", source,
				"--test.provisioner", string(database.ProvisionerTypeEcho),
			}
			inv, root := clitest.New(t, args...)
			clitest.SetupConfig(t, client, root)

			return inv.Run()
		}
		del := func() error {
			args := []string{
				"templates",
				"delete",
				"my-template",
				"--yes",
			}
			inv, root := clitest.New(t, args...)
			clitest.SetupConfig(t, client, root)

			return inv.Run()
		}

		err := create()
		require.NoError(t, err, "Template must be created without error")
		err = del()
		require.NoError(t, err, "Template must be deleted without error")
		err = create()
		require.NoError(t, err, "Template must be recreated without error")
	})

	t.Run("WithVariablesFileWithTheRequiredValue", func(t *testing.T) {
		t.Parallel()

		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		coderdtest.CreateFirstUser(t, client)

		templateVariables := []*proto.TemplateVariable{
			{
				Name:        "first_variable",
				Description: "This is the first variable.",
				Type:        "string",
				Required:    true,
				Sensitive:   true,
			},
			{
				Name:         "second_variable",
				Description:  "This is the second variable",
				Type:         "string",
				DefaultValue: "abc",
				Required:     false,
				Sensitive:    true,
			},
		}
		source := clitest.CreateTemplateVersionSource(t,
			createEchoResponsesWithTemplateVariables(templateVariables))
		tempDir := t.TempDir()
		removeTmpDirUntilSuccessAfterTest(t, tempDir)
		variablesFile, _ := os.CreateTemp(tempDir, "variables*.yaml")
		_, _ = variablesFile.WriteString(`first_variable: foobar`)
		inv, root := clitest.New(t, "templates", "create", "my-template", "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--variables-file", variablesFile.Name())
		clitest.SetupConfig(t, client, root)
		pty := ptytest.New(t).Attach(inv)

		clitest.Start(t, inv)

		matches := []struct {
			match string
			write string
		}{
			{match: "Upload", write: "yes"},
			{match: "Confirm create?", write: "yes"},
		}
		for _, m := range matches {
			pty.ExpectMatch(m.match)
			if len(m.write) > 0 {
				pty.WriteLine(m.write)
			}
		}
	})
	t.Run("WithVariableOption", func(t *testing.T) {
		t.Parallel()

		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		coderdtest.CreateFirstUser(t, client)

		templateVariables := []*proto.TemplateVariable{
			{
				Name:        "first_variable",
				Description: "This is the first variable.",
				Type:        "string",
				Required:    true,
				Sensitive:   true,
			},
		}
		source := clitest.CreateTemplateVersionSource(t,
			createEchoResponsesWithTemplateVariables(templateVariables))
		inv, root := clitest.New(t, "templates", "create", "my-template", "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--variable", "first_variable=foobar")
		clitest.SetupConfig(t, client, root)
		pty := ptytest.New(t).Attach(inv)

		clitest.Start(t, inv)

		matches := []struct {
			match string
			write string
		}{
			{match: "Upload", write: "yes"},
			{match: "Confirm create?", write: "yes"},
		}
		for _, m := range matches {
			pty.ExpectMatch(m.match)
			if len(m.write) > 0 {
				pty.WriteLine(m.write)
			}
		}
	})

	t.Run("RequireActiveVersionInvalid", func(t *testing.T) {
		t.Parallel()

		client := coderdtest.New(t, &coderdtest.Options{
			IncludeProvisionerDaemon: true,
		})
		coderdtest.CreateFirstUser(t, client)
		source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
		args := []string{
			"templates",
			"create",
			"my-template",
			"--directory", source,
			"--test.provisioner", string(database.ProvisionerTypeEcho),
			"--require-active-version",
		}
		inv, root := clitest.New(t, args...)
		clitest.SetupConfig(t, client, root)

		err := inv.Run()
		require.Error(t, err)
		require.Contains(t, err.Error(), "your deployment appears to be an AGPL deployment, so you cannot set enterprise-only flags")
	})
}
