package cli_test

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"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/coderd/database/dbfake"
	"github.com/coder/coder/v2/coderd/rbac"
	"github.com/coder/coder/v2/coderd/util/ptr"
	"github.com/coder/coder/v2/codersdk"
	"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
	"github.com/coder/coder/v2/enterprise/coderd/license"
	"github.com/coder/coder/v2/testutil"
)

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

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

		ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
			LicenseOptions: &coderdenttest.LicenseOptions{
				Features: license.Features{
					codersdk.FeatureAccessControl: 1,
				},
			},
			Options: &coderdtest.Options{
				IncludeProvisionerDaemon: true,
			},
		})

		templateAdmin, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
		version := coderdtest.CreateTemplateVersion(t, templateAdmin, owner.OrganizationID, nil)
		_ = coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, version.ID)
		template := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, version.ID)
		require.False(t, template.RequireActiveVersion)

		inv, conf := newCLI(t, "templates",
			"edit", template.Name,
			"--require-active-version",
			"-y",
		)

		clitest.SetupConfig(t, templateAdmin, conf)

		err := inv.Run()
		require.NoError(t, err)

		ctx := testutil.Context(t, testutil.WaitMedium)
		template, err = templateAdmin.Template(ctx, template.ID)
		require.NoError(t, err)
		require.True(t, template.RequireActiveVersion)
	})

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

		client, owner := coderdenttest.New(t, &coderdenttest.Options{
			LicenseOptions: &coderdenttest.LicenseOptions{
				Features: license.Features{},
			},
			Options: &coderdtest.Options{
				IncludeProvisionerDaemon: true,
			},
		})
		templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())

		version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
		_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
		template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
		require.False(t, template.RequireActiveVersion)

		inv, conf := newCLI(t, "templates",
			"edit", template.Name,
			"--require-active-version",
			"-y",
		)

		clitest.SetupConfig(t, templateAdmin, conf)

		err := inv.Run()
		require.Error(t, err)
		require.Contains(t, err.Error(), "your license is not entitled to use enterprise access control, so you cannot set --require-active-version")
	})

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

		ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
			LicenseOptions: &coderdenttest.LicenseOptions{
				Features: license.Features{
					codersdk.FeatureAdvancedTemplateScheduling: 1,
				},
			},
			Options: &coderdtest.Options{
				IncludeProvisionerDaemon: true,
			},
		})

		templateAdmin, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
		version := coderdtest.CreateTemplateVersion(t, templateAdmin, owner.OrganizationID, nil)
		_ = coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, version.ID)
		template := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, version.ID)
		require.False(t, template.RequireActiveVersion)
		const (
			expectedFailureTTL           = time.Hour * 3
			expectedDormancyThreshold    = time.Hour * 4
			expectedDormancyAutoDeletion = time.Minute * 10
		)
		inv, conf := newCLI(t, "templates",
			"edit", template.Name,
			"--failure-ttl="+expectedFailureTTL.String(),
			"--dormancy-threshold="+expectedDormancyThreshold.String(),
			"--dormancy-auto-deletion="+expectedDormancyAutoDeletion.String(),
			"-y",
		)

		clitest.SetupConfig(t, templateAdmin, conf)

		err := inv.Run()
		require.NoError(t, err)

		ctx := testutil.Context(t, testutil.WaitMedium)
		template, err = templateAdmin.Template(ctx, template.ID)
		require.NoError(t, err)
		require.Equal(t, expectedFailureTTL.Milliseconds(), template.FailureTTLMillis)
		require.Equal(t, expectedDormancyThreshold.Milliseconds(), template.TimeTilDormantMillis)
		require.Equal(t, expectedDormancyAutoDeletion.Milliseconds(), template.TimeTilDormantAutoDeleteMillis)

		inv, conf = newCLI(t, "templates",
			"edit", template.Name,
			"--display-name=idc",
			"-y",
		)

		clitest.SetupConfig(t, templateAdmin, conf)

		err = inv.Run()
		require.NoError(t, err)

		// Refetch the template to assert we haven't inadvertently updated
		// the values to their default values.
		template, err = templateAdmin.Template(ctx, template.ID)
		require.NoError(t, err)
		require.Equal(t, expectedFailureTTL.Milliseconds(), template.FailureTTLMillis)
		require.Equal(t, expectedDormancyThreshold.Milliseconds(), template.TimeTilDormantMillis)
		require.Equal(t, expectedDormancyAutoDeletion.Milliseconds(), template.TimeTilDormantAutoDeleteMillis)
	})

	// Test that omitting a flag does not update a template with the
	// default for a flag.
	t.Run("DefaultsDontOverride", func(t *testing.T) {
		t.Parallel()

		ctx := testutil.Context(t, testutil.WaitMedium)
		ownerClient, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
			LicenseOptions: &coderdenttest.LicenseOptions{
				Features: license.Features{
					codersdk.FeatureAdvancedTemplateScheduling: 1,
					codersdk.FeatureAccessControl:              1,
					codersdk.FeatureTemplateRBAC:               1,
				},
			},
		})

		dbtemplate := dbfake.TemplateVersion(t, db).Seed(database.TemplateVersion{
			CreatedBy:      owner.UserID,
			OrganizationID: owner.OrganizationID,
		}).Do().Template

		var (
			expectedName                 = "template"
			expectedDisplayName          = "template_display"
			expectedDescription          = "My description"
			expectedIcon                 = "icon.pjg"
			expectedDefaultTTLMillis     = time.Hour.Milliseconds()
			expectedAllowAutostart       = false
			expectedAllowAutostop        = false
			expectedFailureTTLMillis     = time.Minute.Milliseconds()
			expectedDormancyMillis       = 2 * time.Minute.Milliseconds()
			expectedAutoDeleteMillis     = 3 * time.Minute.Milliseconds()
			expectedRequireActiveVersion = true
			expectedAllowCancelJobs      = false
			deprecationMessage           = "Deprecate me"
			expectedDisableEveryone      = true
			expectedAutostartDaysOfWeek  = []string{}
			expectedAutoStopDaysOfWeek   = []string{}
			expectedAutoStopWeeks        = 1
		)

		assertFieldsFn := func(t *testing.T, tpl codersdk.Template, acl codersdk.TemplateACL) {
			t.Helper()

			assert.Equal(t, expectedName, tpl.Name)
			assert.Equal(t, expectedDisplayName, tpl.DisplayName)
			assert.Equal(t, expectedDescription, tpl.Description)
			assert.Equal(t, expectedIcon, tpl.Icon)
			assert.Equal(t, expectedDefaultTTLMillis, tpl.DefaultTTLMillis)
			assert.Equal(t, expectedAllowAutostart, tpl.AllowUserAutostart)
			assert.Equal(t, expectedAllowAutostop, tpl.AllowUserAutostop)
			assert.Equal(t, expectedFailureTTLMillis, tpl.FailureTTLMillis)
			assert.Equal(t, expectedDormancyMillis, tpl.TimeTilDormantMillis)
			assert.Equal(t, expectedAutoDeleteMillis, tpl.TimeTilDormantAutoDeleteMillis)
			assert.Equal(t, expectedRequireActiveVersion, tpl.RequireActiveVersion)
			assert.Equal(t, deprecationMessage, tpl.DeprecationMessage)
			assert.Equal(t, expectedAllowCancelJobs, tpl.AllowUserCancelWorkspaceJobs)
			assert.Equal(t, len(acl.Groups) == 0, expectedDisableEveryone)
			assert.Equal(t, expectedAutostartDaysOfWeek, tpl.AutostartRequirement.DaysOfWeek)
			assert.Equal(t, expectedAutoStopDaysOfWeek, tpl.AutostopRequirement.DaysOfWeek)
			assert.Equal(t, int64(expectedAutoStopWeeks), tpl.AutostopRequirement.Weeks)
		}

		template, err := ownerClient.UpdateTemplateMeta(ctx, dbtemplate.ID, codersdk.UpdateTemplateMeta{
			Name:                           expectedName,
			DisplayName:                    expectedDisplayName,
			Description:                    expectedDescription,
			Icon:                           expectedIcon,
			DefaultTTLMillis:               expectedDefaultTTLMillis,
			AllowUserAutostop:              expectedAllowAutostop,
			AllowUserAutostart:             expectedAllowAutostart,
			FailureTTLMillis:               expectedFailureTTLMillis,
			TimeTilDormantMillis:           expectedDormancyMillis,
			TimeTilDormantAutoDeleteMillis: expectedAutoDeleteMillis,
			RequireActiveVersion:           expectedRequireActiveVersion,
			DeprecationMessage:             ptr.Ref(deprecationMessage),
			DisableEveryoneGroupAccess:     expectedDisableEveryone,
			AllowUserCancelWorkspaceJobs:   expectedAllowCancelJobs,
			AutostartRequirement: &codersdk.TemplateAutostartRequirement{
				DaysOfWeek: expectedAutostartDaysOfWeek,
			},
		})
		require.NoError(t, err)

		templateACL, err := ownerClient.TemplateACL(ctx, template.ID)
		require.NoError(t, err)

		assertFieldsFn(t, template, templateACL)

		expectedName = "newName"
		inv, conf := newCLI(t, "templates",
			"edit", template.Name,
			"--name=newName",
			"-y",
		)

		clitest.SetupConfig(t, ownerClient, conf)

		err = inv.Run()
		require.NoError(t, err)

		template, err = ownerClient.Template(ctx, template.ID)
		require.NoError(t, err)
		templateACL, err = ownerClient.TemplateACL(ctx, template.ID)
		require.NoError(t, err)
		assertFieldsFn(t, template, templateACL)

		expectedAutostartDaysOfWeek = []string{"monday", "wednesday", "friday"}
		expectedAutoStopDaysOfWeek = []string{"tuesday", "thursday"}
		expectedAutoStopWeeks = 2

		template, err = ownerClient.UpdateTemplateMeta(ctx, dbtemplate.ID, codersdk.UpdateTemplateMeta{
			Name:                           expectedName,
			DisplayName:                    expectedDisplayName,
			Description:                    expectedDescription,
			Icon:                           expectedIcon,
			DefaultTTLMillis:               expectedDefaultTTLMillis,
			AllowUserAutostop:              expectedAllowAutostop,
			AllowUserAutostart:             expectedAllowAutostart,
			FailureTTLMillis:               expectedFailureTTLMillis,
			TimeTilDormantMillis:           expectedDormancyMillis,
			TimeTilDormantAutoDeleteMillis: expectedAutoDeleteMillis,
			RequireActiveVersion:           expectedRequireActiveVersion,
			DeprecationMessage:             ptr.Ref(deprecationMessage),
			DisableEveryoneGroupAccess:     expectedDisableEveryone,
			AllowUserCancelWorkspaceJobs:   expectedAllowCancelJobs,
			AutostartRequirement: &codersdk.TemplateAutostartRequirement{
				DaysOfWeek: expectedAutostartDaysOfWeek,
			},

			AutostopRequirement: &codersdk.TemplateAutostopRequirement{
				DaysOfWeek: expectedAutoStopDaysOfWeek,
				Weeks:      int64(expectedAutoStopWeeks),
			},
		})
		require.NoError(t, err)
		assertFieldsFn(t, template, templateACL)

		// Rerun the update so we can assert that autostop days aren't
		// mucked with.
		expectedName = "newName2"
		inv, conf = newCLI(t, "templates",
			"edit", template.Name,
			"--name=newName2",
			"-y",
		)

		clitest.SetupConfig(t, ownerClient, conf)

		err = inv.Run()
		require.NoError(t, err)

		template, err = ownerClient.Template(ctx, template.ID)
		require.NoError(t, err)

		templateACL, err = ownerClient.TemplateACL(ctx, template.ID)
		require.NoError(t, err)
		assertFieldsFn(t, template, templateACL)
	})
}
