package kubernetes

import (
	"fmt"
	"sort"

	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/cli/command/stack/formatter"
	"github.com/docker/cli/cli/command/stack/options"
	"github.com/docker/cli/cli/command/task"
	"github.com/docker/docker/api/types/swarm"
	apiv1 "k8s.io/api/core/v1"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)

var supportedPSFilters = map[string]bool{
	"name":    true,
	"service": true,
	"node":    true,
}

// RunPS is the kubernetes implementation of docker stack ps
func RunPS(dockerCli *KubeCli, options options.PS) error {
	filters := options.Filter.Value()
	if err := filters.Validate(supportedPSFilters); err != nil {
		return err
	}
	client, err := dockerCli.composeClient()
	if err != nil {
		return err
	}
	stacks, err := client.Stacks(false)
	if err != nil {
		return err
	}
	stackName := options.Namespace
	_, err = stacks.Get(stackName)
	if apierrs.IsNotFound(err) {
		return fmt.Errorf("nothing found in stack: %s", stackName)
	}
	if err != nil {
		return err
	}
	pods, err := fetchPods(stackName, client.Pods(), filters)
	if err != nil {
		return err
	}
	if len(pods) == 0 {
		return fmt.Errorf("nothing found in stack: %s", stackName)
	}
	return printTasks(dockerCli, options, stackName, client, pods)
}

func printTasks(dockerCli command.Cli, options options.PS, namespace string, client corev1.NodesGetter, pods []apiv1.Pod) error {
	format := options.Format
	if format == "" {
		format = task.DefaultFormat(dockerCli.ConfigFile(), options.Quiet)
	}

	tasks := make([]swarm.Task, len(pods))
	for i, pod := range pods {
		tasks[i] = podToTask(pod)
	}
	sort.Stable(tasksBySlot(tasks))

	names := map[string]string{}
	nodes := map[string]string{}

	n, err := listNodes(client, options.NoResolve)
	if err != nil {
		return err
	}
	for i, task := range tasks {
		nodeValue, err := resolveNode(pods[i].Spec.NodeName, n, options.NoResolve)
		if err != nil {
			return err
		}
		names[task.ID] = fmt.Sprintf("%s_%s", namespace, pods[i].Name)
		nodes[task.ID] = nodeValue
	}

	tasksCtx := formatter.Context{
		Output: dockerCli.Out(),
		Format: task.NewTaskFormat(format, options.Quiet),
		Trunc:  !options.NoTrunc,
	}

	return task.FormatWrite(tasksCtx, tasks, names, nodes)
}

func resolveNode(name string, nodes *apiv1.NodeList, noResolve bool) (string, error) {
	// Here we have a name and we need to resolve its identifier. To mimic swarm behavior
	// we need to resolve to the id when noResolve is set, otherwise we return the name.
	if noResolve {
		for _, node := range nodes.Items {
			if node.Name == name {
				return string(node.UID), nil
			}
		}
		return "", fmt.Errorf("could not find node '%s'", name)
	}
	return name, nil
}

func listNodes(client corev1.NodesGetter, noResolve bool) (*apiv1.NodeList, error) {
	if noResolve {
		return client.Nodes().List(metav1.ListOptions{})
	}
	return nil, nil
}
