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

package client

import (
	"sync"
	"testing"

	"github.com/hashicorp/nomad/ci"
	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
	"github.com/hashicorp/nomad/client/allocrunner/state"
	"github.com/hashicorp/nomad/client/config"
	cinterfaces "github.com/hashicorp/nomad/client/interfaces"
	"github.com/hashicorp/nomad/client/pluginmanager/drivermanager"
	cstructs "github.com/hashicorp/nomad/client/structs"
	"github.com/hashicorp/nomad/nomad/mock"
	"github.com/hashicorp/nomad/nomad/structs"
	"github.com/hashicorp/nomad/plugins/device"
	"github.com/hashicorp/nomad/plugins/drivers"
	"github.com/hashicorp/nomad/testutil"
)

// TestEmptyAllocRunner demonstrates the minimum interface necessary to
// implement a mock AllocRunner that can report client status back to the server
func TestEmptyAllocRunner(t *testing.T) {
	ci.Parallel(t)

	s1, _, cleanupS1 := testServer(t, nil)
	defer cleanupS1()

	_, cleanup := TestClient(t, func(c *config.Config) {
		c.RPCHandler = s1
		c.AllocRunnerFactory = newEmptyAllocRunnerFunc
	})
	defer cleanup()

	job := mock.Job()
	job.Constraints = nil
	job.TaskGroups[0].Constraints = nil
	job.TaskGroups[0].Count = 1
	task := job.TaskGroups[0].Tasks[0]
	task.Driver = "mock_driver"
	task.Config = map[string]interface{}{
		"run_for": "10s",
	}
	task.Services = nil

	// WaitForRunning polls the server until the ClientStatus is running
	testutil.WaitForRunning(t, s1.RPC, job)
}

type emptyAllocRunner struct {
	c          cinterfaces.AllocStateHandler
	alloc      *structs.Allocation
	allocState *state.State
	allocLock  sync.RWMutex
}

func newEmptyAllocRunnerFunc(conf *config.AllocRunnerConfig) (interfaces.AllocRunner, error) {
	return &emptyAllocRunner{
		c:          conf.StateUpdater,
		alloc:      conf.Alloc,
		allocState: &state.State{},
	}, nil
}

func (ar *emptyAllocRunner) Alloc() *structs.Allocation {
	ar.allocLock.RLock()
	defer ar.allocLock.RUnlock()
	return ar.alloc.Copy()
}

func (ar *emptyAllocRunner) Run() {
	ar.allocLock.RLock()
	defer ar.allocLock.RUnlock()
	ar.alloc.ClientStatus = "running"
	ar.c.AllocStateUpdated(ar.alloc)
}

func (ar *emptyAllocRunner) Restore() error { return nil }
func (ar *emptyAllocRunner) Update(update *structs.Allocation) {
	ar.allocLock.Lock()
	defer ar.allocLock.Unlock()
	ar.alloc = update
}

func (ar *emptyAllocRunner) Reconnect(update *structs.Allocation) error {
	ar.allocLock.Lock()
	defer ar.allocLock.Unlock()
	ar.alloc = update
	return nil
}

func (ar *emptyAllocRunner) Shutdown() {}
func (ar *emptyAllocRunner) Destroy()  {}

func (ar *emptyAllocRunner) IsDestroyed() bool { return false }
func (ar *emptyAllocRunner) IsMigrating() bool { return false }
func (ar *emptyAllocRunner) IsWaiting() bool   { return false }

func (ar *emptyAllocRunner) WaitCh() <-chan struct{} { return make(chan struct{}) }

func (ar *emptyAllocRunner) DestroyCh() <-chan struct{} {
	ch := make(chan struct{})
	close(ch)
	return ch
}

func (ar *emptyAllocRunner) ShutdownCh() <-chan struct{} {
	ch := make(chan struct{})
	close(ch)
	return ch
}

func (ar *emptyAllocRunner) AllocState() *state.State {
	ar.allocLock.RLock()
	defer ar.allocLock.RUnlock()
	return ar.allocState.Copy()
}

func (ar *emptyAllocRunner) PersistState() error           { return nil }
func (ar *emptyAllocRunner) AcknowledgeState(*state.State) {}
func (ar *emptyAllocRunner) GetUpdatePriority(*structs.Allocation) cstructs.AllocUpdatePriority {
	return cstructs.AllocUpdatePriorityUrgent
}

func (ar *emptyAllocRunner) SetClientStatus(status string) {
	ar.allocLock.Lock()
	defer ar.allocLock.Unlock()
	ar.alloc.ClientStatus = status
}

func (ar *emptyAllocRunner) Signal(taskName, signal string) error { return nil }
func (ar *emptyAllocRunner) RestartTask(taskName string, taskEvent *structs.TaskEvent) error {
	return nil
}
func (ar *emptyAllocRunner) RestartRunning(taskEvent *structs.TaskEvent) error { return nil }
func (ar *emptyAllocRunner) RestartAll(taskEvent *structs.TaskEvent) error     { return nil }

func (ar *emptyAllocRunner) GetTaskEventHandler(taskName string) drivermanager.EventHandler {
	return nil
}
func (ar *emptyAllocRunner) GetTaskExecHandler(taskName string) drivermanager.TaskExecHandler {
	return nil
}
func (ar *emptyAllocRunner) GetTaskDriverCapabilities(taskName string) (*drivers.Capabilities, error) {
	return nil, nil
}

func (ar *emptyAllocRunner) StatsReporter() interfaces.AllocStatsReporter { return ar }
func (ar *emptyAllocRunner) Listener() *cstructs.AllocListener            { return nil }
func (ar *emptyAllocRunner) GetAllocDir() *allocdir.AllocDir              { return nil }

// LatestAllocStats lets this empty runner implement AllocStatsReporter
func (ar *emptyAllocRunner) LatestAllocStats(taskFilter string) (*cstructs.AllocResourceUsage, error) {
	return &cstructs.AllocResourceUsage{
		ResourceUsage: &cstructs.ResourceUsage{
			MemoryStats: &cstructs.MemoryStats{},
			CpuStats:    &cstructs.CpuStats{},
			DeviceStats: []*device.DeviceGroupStats{},
		},
		Tasks:     map[string]*cstructs.TaskResourceUsage{},
		Timestamp: 0,
	}, nil
}
