package pods

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"strings"
	"text/tabwriter"
	"text/template"
	"time"

	"github.com/docker/go-units"

	"github.com/containers/libpod/cmd/podmanV2/registry"
	"github.com/containers/libpod/pkg/domain/entities"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

var (
	psDescription = "List all pods on system including their names, ids and current state."

	// Command: podman pod _ps_
	psCmd = &cobra.Command{
		Use:     "ps",
		Aliases: []string{"ls", "list"},
		Short:   "list pods",
		Long:    psDescription,
		RunE:    pods,
	}
)

var (
	defaultHeaders string = "POD ID\tNAME\tSTATUS\tCREATED"
	inputFilters   string
	noTrunc        bool
	psInput        entities.PodPSOptions
)

func init() {
	registry.Commands = append(registry.Commands, registry.CliCommand{
		Mode:    []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
		Command: psCmd,
		Parent:  podCmd,
	})
	flags := psCmd.Flags()
	flags.BoolVar(&psInput.CtrNames, "ctr-names", false, "Display the container names")
	flags.BoolVar(&psInput.CtrIds, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated")
	flags.BoolVar(&psInput.CtrStatus, "ctr-status", false, "Display the container status")
	// TODO should we make this a [] ?
	flags.StringVarP(&inputFilters, "filter", "f", "", "Filter output based on conditions given")
	flags.StringVar(&psInput.Format, "format", "", "Pretty-print pods to JSON or using a Go template")
	flags.BoolVarP(&psInput.Latest, "latest", "l", false, "Act on the latest pod podman is aware of")
	flags.BoolVar(&psInput.Namespace, "namespace", false, "Display namespace information of the pod")
	flags.BoolVar(&psInput.Namespace, "ns", false, "Display namespace information of the pod")
	flags.BoolVar(&noTrunc, "no-trunc", false, "Do not truncate pod and container IDs")
	flags.BoolVarP(&psInput.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only")
	flags.StringVar(&psInput.Sort, "sort", "created", "Sort output by created, id, name, or number")
	if registry.IsRemote() {
		_ = flags.MarkHidden("latest")
	}
}

func pods(cmd *cobra.Command, args []string) error {
	var (
		w   io.Writer = os.Stdout
		row string
		lpr []ListPodReporter
	)
	if cmd.Flag("filter").Changed {
		for _, f := range strings.Split(inputFilters, ",") {
			split := strings.Split(f, "=")
			if len(split) < 2 {
				return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
			}
			psInput.Filters[split[0]] = append(psInput.Filters[split[0]], split[1])
		}
	}
	responses, err := registry.ContainerEngine().PodPs(context.Background(), psInput)
	if err != nil {
		return err
	}

	if psInput.Format == "json" {
		b, err := json.MarshalIndent(responses, "", "  ")
		if err != nil {
			return err
		}
		fmt.Println(string(b))
		return nil
	}

	for _, r := range responses {
		lpr = append(lpr, ListPodReporter{r})
	}
	headers, row := createPodPsOut()
	if psInput.Quiet {
		if noTrunc {
			row = "{{.Id}}\n"
		} else {
			row = "{{slice .Id 0 12}}\n"
		}
	}
	if cmd.Flag("format").Changed {
		row = psInput.Format
		if !strings.HasPrefix(row, "\n") {
			row += "\n"
		}
	}
	format := "{{range . }}" + row + "{{end}}"
	if !psInput.Quiet && !cmd.Flag("format").Changed {
		format = headers + format
	}
	tmpl, err := template.New("listPods").Parse(format)
	if err != nil {
		return err
	}
	if !psInput.Quiet {
		w = tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
	}
	if err := tmpl.Execute(w, lpr); err != nil {
		return err
	}
	if flusher, ok := w.(interface{ Flush() error }); ok {
		return flusher.Flush()
	}
	return nil
}

func createPodPsOut() (string, string) {
	var row string
	headers := defaultHeaders
	if noTrunc {
		row += "{{.Id}}"
	} else {
		row += "{{slice .Id 0 12}}"
	}

	row += "\t{{.Name}}\t{{.Status}}\t{{.Created}}"

	if psInput.CtrIds {
		headers += "\tIDS"
		row += "\t{{.ContainerIds}}"
	}
	if psInput.CtrNames {
		headers += "\tNAMES"
		row += "\t{{.ContainerNames}}"
	}
	if psInput.CtrStatus {
		headers += "\tSTATUS"
		row += "\t{{.ContainerStatuses}}"
	}
	if psInput.Namespace {
		headers += "\tCGROUP\tNAMESPACES"
		row += "\t{{.Cgroup}}\t{{.Namespace}}"
	}
	if !psInput.CtrStatus && !psInput.CtrNames && !psInput.CtrIds {
		headers += "\t# OF CONTAINERS"
		row += "\t{{.NumberOfContainers}}"

	}
	headers += "\tINFRA ID\n"
	if noTrunc {
		row += "\t{{.InfraId}}\n"
	} else {
		row += "\t{{slice .InfraId 0 12}}\n"
	}
	return headers, row
}

// ListPodReporter is a struct for pod ps output
type ListPodReporter struct {
	*entities.ListPodsReport
}

// Created returns a human readable created time/date
func (l ListPodReporter) Created() string {
	return units.HumanDuration(time.Since(l.ListPodsReport.Created)) + " ago"
}

// NumberofContainers returns an int representation for
// the number of containers belonging to the pod
func (l ListPodReporter) NumberOfContainers() int {
	return len(l.Containers)
}

// Added for backwards compatibility with podmanv1
func (l ListPodReporter) InfraID() string {
	return l.InfraId()
}

// InfraId returns the infra container id for the pod
// depending on trunc
func (l ListPodReporter) InfraId() string {
	if noTrunc {
		return l.ListPodsReport.InfraId
	}
	return l.ListPodsReport.InfraId[0:12]
}

func (l ListPodReporter) ContainerIds() string {
	var ctrids []string
	for _, c := range l.Containers {
		id := c.Id
		if !noTrunc {
			id = id[0:12]
		}
		ctrids = append(ctrids, id)
	}
	return strings.Join(ctrids, ",")
}

func (l ListPodReporter) ContainerNames() string {
	var ctrNames []string
	for _, c := range l.Containers {
		ctrNames = append(ctrNames, c.Names)
	}
	return strings.Join(ctrNames, ",")
}

func (l ListPodReporter) ContainerStatuses() string {
	var statuses []string
	for _, c := range l.Containers {
		statuses = append(statuses, c.Status)
	}
	return strings.Join(statuses, ",")
}
