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

package consul

import (
	"fmt"
	"time"

	"github.com/hashicorp/nomad/e2e/e2eutil"
	"github.com/hashicorp/nomad/e2e/framework"
	"github.com/hashicorp/nomad/helper/uuid"
	"github.com/hashicorp/nomad/testutil"
)

type OnUpdateChecksTest struct {
	framework.TC
	jobIDs []string
}

func (tc *OnUpdateChecksTest) BeforeAll(f *framework.F) {
	// Ensure cluster has leader before running tests
	e2eutil.WaitForLeader(f.T(), tc.Nomad())
	// Ensure that we have at least 1 client node in ready state
	e2eutil.WaitForNodesReady(f.T(), tc.Nomad(), 1)
}

func (tc *OnUpdateChecksTest) AfterEach(f *framework.F) {
	nomadClient := tc.Nomad()
	j := nomadClient.Jobs()

	for _, id := range tc.jobIDs {
		j.Deregister(id, true, nil)
	}
	_, err := e2eutil.Command("nomad", "system", "gc")
	f.NoError(err)
}

// TestOnUpdateCheck_IgnoreWarning_IgnoreErrors ensures that deployments
// complete successfully with service checks that warn and error when on_update
// is specified to ignore either.
func (tc *OnUpdateChecksTest) TestOnUpdateCheck_IgnoreWarning_IgnoreErrors(f *framework.F) {
	uuid := uuid.Generate()
	jobID := fmt.Sprintf("on-update-%s", uuid[0:8])
	tc.jobIDs = append(tc.jobIDs, jobID)

	f.NoError(
		e2eutil.Register(jobID, "consul/input/on_update.nomad"),
		"should have registered successfully",
	)

	wc := &e2eutil.WaitConfig{
		Interval: 1 * time.Second,
		Retries:  60,
	}
	f.NoError(
		e2eutil.WaitForLastDeploymentStatus(jobID, "", "successful", wc),
		"deployment should have completed successfully",
	)

	// register update with on_update = ignore
	// this check errors, deployment should still be successful
	f.NoError(
		e2eutil.Register(jobID, "consul/input/on_update_2.nomad"),
		"should have registered successfully",
	)

	f.NoError(
		e2eutil.WaitForLastDeploymentStatus(jobID, "", "successful", wc),
		"deployment should have completed successfully",
	)
}

// TestOnUpdate_CheckRestart ensures that a service check set to ignore
// warnings still follows the check_restart block if the task becomes
// unhealthy after a deployment is successful.  on_update_check_restart has a
// script check that should report as a warning status for the deployment to
// become healthy. The script check then reports unhealthy and the
// check_restart policy should restart the task
func (tc *OnUpdateChecksTest) TestOnUpdate_CheckRestart(f *framework.F) {
	uuid := uuid.Generate()
	jobID := fmt.Sprintf("on-update-restart-%s", uuid[0:8])
	tc.jobIDs = append(tc.jobIDs, jobID)

	f.NoError(
		e2eutil.Register(jobID, "consul/input/on_update_check_restart.nomad"),
		"should have registered successfully",
	)

	wc := &e2eutil.WaitConfig{
		Interval: 1 * time.Second,
		Retries:  60,
	}
	f.NoError(
		e2eutil.WaitForLastDeploymentStatus(jobID, "", "successful", wc),
		"deployment should have completed successfully",
	)

	// Wait for and ensure that allocation restarted
	testutil.WaitForResultRetries(wc.Retries, func() (bool, error) {
		time.Sleep(wc.Interval)
		allocs, err := e2eutil.AllocTaskEventsForJob(jobID, "")
		if err != nil {
			return false, err
		}

		for allocID, allocEvents := range allocs {
			var allocRestarted bool
			var eventTypes []string
			for _, events := range allocEvents {
				eventTypes = append(eventTypes, events["Type"])
				if events["Type"] == "Restart Signaled" {
					allocRestarted = true
				}
			}
			if allocRestarted {
				return true, nil
			}
			return false, fmt.Errorf("alloc %s expected to restart got %v", allocID, eventTypes)
		}

		return true, nil
	}, func(err error) {
		f.NoError(err)
	})
}
