package daemon // import "github.com/docker/docker/integration-cli/daemon"

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/errdefs"
	"gotest.tools/v3/assert"
)

// CheckServiceTasksInState returns the number of tasks with a matching state,
// and optional message substring.
func (d *Daemon) CheckServiceTasksInState(ctx context.Context, service string, state swarm.TaskState, message string) func(*testing.T) (interface{}, string) {
	return func(c *testing.T) (interface{}, string) {
		tasks := d.GetServiceTasks(ctx, c, service)
		var count int
		for _, task := range tasks {
			if task.Status.State == state {
				if message == "" || strings.Contains(task.Status.Message, message) {
					count++
				}
			}
		}
		return count, ""
	}
}

// CheckServiceTasksInStateWithError returns the number of tasks with a matching state,
// and optional message substring.
func (d *Daemon) CheckServiceTasksInStateWithError(ctx context.Context, service string, state swarm.TaskState, errorMessage string) func(*testing.T) (interface{}, string) {
	return func(c *testing.T) (interface{}, string) {
		tasks := d.GetServiceTasks(ctx, c, service)
		var count int
		for _, task := range tasks {
			if task.Status.State == state {
				if errorMessage == "" || strings.Contains(task.Status.Err, errorMessage) {
					count++
				}
			}
		}
		return count, ""
	}
}

// CheckServiceRunningTasks returns the number of running tasks for the specified service
func (d *Daemon) CheckServiceRunningTasks(ctx context.Context, service string) func(*testing.T) (interface{}, string) {
	return d.CheckServiceTasksInState(ctx, service, swarm.TaskStateRunning, "")
}

// CheckServiceUpdateState returns the current update state for the specified service
func (d *Daemon) CheckServiceUpdateState(ctx context.Context, service string) func(*testing.T) (interface{}, string) {
	return func(c *testing.T) (interface{}, string) {
		service := d.GetService(ctx, c, service)
		if service.UpdateStatus == nil {
			return "", ""
		}
		return service.UpdateStatus.State, ""
	}
}

// CheckPluginRunning returns the runtime state of the plugin
func (d *Daemon) CheckPluginRunning(ctx context.Context, plugin string) func(c *testing.T) (interface{}, string) {
	return func(c *testing.T) (interface{}, string) {
		apiclient := d.NewClientT(c)
		resp, _, err := apiclient.PluginInspectWithRaw(ctx, plugin)
		if errdefs.IsNotFound(err) {
			return false, fmt.Sprintf("%v", err)
		}
		assert.NilError(c, err)
		return resp.Enabled, fmt.Sprintf("%+v", resp)
	}
}

// CheckPluginImage returns the runtime state of the plugin
func (d *Daemon) CheckPluginImage(ctx context.Context, plugin string) func(c *testing.T) (interface{}, string) {
	return func(c *testing.T) (interface{}, string) {
		apiclient := d.NewClientT(c)
		resp, _, err := apiclient.PluginInspectWithRaw(ctx, plugin)
		if errdefs.IsNotFound(err) {
			return false, fmt.Sprintf("%v", err)
		}
		assert.NilError(c, err)
		return resp.PluginReference, fmt.Sprintf("%+v", resp)
	}
}

// CheckServiceTasks returns the number of tasks for the specified service
func (d *Daemon) CheckServiceTasks(ctx context.Context, service string) func(*testing.T) (interface{}, string) {
	return func(c *testing.T) (interface{}, string) {
		tasks := d.GetServiceTasks(ctx, c, service)
		return len(tasks), ""
	}
}

// CheckRunningTaskNetworks returns the number of times each network is referenced from a task.
func (d *Daemon) CheckRunningTaskNetworks(ctx context.Context) func(t *testing.T) (interface{}, string) {
	return func(t *testing.T) (interface{}, string) {
		cli := d.NewClientT(t)
		defer cli.Close()

		tasks, err := cli.TaskList(ctx, types.TaskListOptions{
			Filters: filters.NewArgs(filters.Arg("desired-state", "running")),
		})
		assert.NilError(t, err)

		result := make(map[string]int)
		for _, task := range tasks {
			for _, network := range task.Spec.Networks {
				result[network.Target]++
			}
		}
		return result, ""
	}
}

// CheckRunningTaskImages returns the times each image is running as a task.
func (d *Daemon) CheckRunningTaskImages(ctx context.Context) func(t *testing.T) (interface{}, string) {
	return func(t *testing.T) (interface{}, string) {
		cli := d.NewClientT(t)
		defer cli.Close()

		tasks, err := cli.TaskList(ctx, types.TaskListOptions{
			Filters: filters.NewArgs(filters.Arg("desired-state", "running")),
		})
		assert.NilError(t, err)

		result := make(map[string]int)
		for _, task := range tasks {
			if task.Status.State == swarm.TaskStateRunning && task.Spec.ContainerSpec != nil {
				result[task.Spec.ContainerSpec.Image]++
			}
		}
		return result, ""
	}
}

// CheckNodeReadyCount returns the number of ready node on the swarm
func (d *Daemon) CheckNodeReadyCount(ctx context.Context) func(t *testing.T) (interface{}, string) {
	return func(t *testing.T) (interface{}, string) {
		nodes := d.ListNodes(ctx, t)
		var readyCount int
		for _, node := range nodes {
			if node.Status.State == swarm.NodeStateReady {
				readyCount++
			}
		}
		return readyCount, ""
	}
}

// CheckLocalNodeState returns the current swarm node state
func (d *Daemon) CheckLocalNodeState(ctx context.Context) func(t *testing.T) (interface{}, string) {
	return func(t *testing.T) (interface{}, string) {
		info := d.SwarmInfo(ctx, t)
		return info.LocalNodeState, ""
	}
}

// CheckControlAvailable returns the current swarm control available
func (d *Daemon) CheckControlAvailable(ctx context.Context) func(t *testing.T) (interface{}, string) {
	return func(t *testing.T) (interface{}, string) {
		info := d.SwarmInfo(ctx, t)
		assert.Equal(t, info.LocalNodeState, swarm.LocalNodeStateActive)
		return info.ControlAvailable, ""
	}
}

// CheckLeader returns whether there is a leader on the swarm or not
func (d *Daemon) CheckLeader(ctx context.Context) func(t *testing.T) (interface{}, string) {
	return func(t *testing.T) (interface{}, string) {
		cli := d.NewClientT(t)
		defer cli.Close()

		errList := "could not get node list"

		ls, err := cli.NodeList(ctx, types.NodeListOptions{})
		if err != nil {
			return err, errList
		}

		for _, node := range ls {
			if node.ManagerStatus != nil && node.ManagerStatus.Leader {
				return nil, ""
			}
		}
		return fmt.Errorf("no leader"), "could not find leader"
	}
}

// CmdRetryOutOfSequence tries the specified command against the current daemon
// up to 10 times, retrying if it encounters an "update out of sequence" error.
func (d *Daemon) CmdRetryOutOfSequence(args ...string) (string, error) {
	var (
		output string
		err    error
	)

	for i := 0; i < 10; i++ {
		output, err = d.Cmd(args...)
		// error, no error, whatever. if we don't have "update out of
		// sequence", we don't retry, we just return.
		if !strings.Contains(output, "update out of sequence") {
			return output, err
		}
	}

	// otherwise, once all of our attempts have been exhausted, just return
	// whatever the last values were.
	return output, err
}
