package cli

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"golang.org/x/xerrors"

	"github.com/coder/coder/v2/cli/clibase"
	"github.com/coder/coder/v2/cli/cliui"
	"github.com/coder/coder/v2/codersdk"
	"github.com/coder/pretty"
)

// workspaceListRow is the type provided to the OutputFormatter. This is a bit
// dodgy but it's the only way to do complex display code for one format vs. the
// other.
type workspaceListRow struct {
	// For JSON format:
	codersdk.Workspace `table:"-"`

	// For table format:
	Favorite       bool   `json:"-" table:"favorite"`
	WorkspaceName  string `json:"-" table:"workspace,default_sort"`
	Template       string `json:"-" table:"template"`
	Status         string `json:"-" table:"status"`
	Healthy        string `json:"-" table:"healthy"`
	LastBuilt      string `json:"-" table:"last built"`
	CurrentVersion string `json:"-" table:"current version"`
	Outdated       bool   `json:"-" table:"outdated"`
	StartsAt       string `json:"-" table:"starts at"`
	StartsNext     string `json:"-" table:"starts next"`
	StopsAfter     string `json:"-" table:"stops after"`
	StopsNext      string `json:"-" table:"stops next"`
	DailyCost      string `json:"-" table:"daily cost"`
}

func workspaceListRowFromWorkspace(now time.Time, workspace codersdk.Workspace) workspaceListRow {
	status := codersdk.WorkspaceDisplayStatus(workspace.LatestBuild.Job.Status, workspace.LatestBuild.Transition)

	lastBuilt := now.UTC().Sub(workspace.LatestBuild.Job.CreatedAt).Truncate(time.Second)
	schedRow := scheduleListRowFromWorkspace(now, workspace)

	healthy := ""
	if status == "Starting" || status == "Started" {
		healthy = strconv.FormatBool(workspace.Health.Healthy)
	}
	favIco := " "
	if workspace.Favorite {
		favIco = "★"
	}
	workspaceName := favIco + " " + workspace.OwnerName + "/" + workspace.Name
	return workspaceListRow{
		Favorite:       workspace.Favorite,
		Workspace:      workspace,
		WorkspaceName:  workspaceName,
		Template:       workspace.TemplateName,
		Status:         status,
		Healthy:        healthy,
		LastBuilt:      durationDisplay(lastBuilt),
		CurrentVersion: workspace.LatestBuild.TemplateVersionName,
		Outdated:       workspace.Outdated,
		StartsAt:       schedRow.StartsAt,
		StartsNext:     schedRow.StartsNext,
		StopsAfter:     schedRow.StopsAfter,
		StopsNext:      schedRow.StopsNext,
		DailyCost:      strconv.Itoa(int(workspace.LatestBuild.DailyCost)),
	}
}

func (r *RootCmd) list() *clibase.Cmd {
	var (
		filter    cliui.WorkspaceFilter
		formatter = cliui.NewOutputFormatter(
			cliui.TableFormat(
				[]workspaceListRow{},
				[]string{
					"workspace",
					"template",
					"status",
					"healthy",
					"last built",
					"current version",
					"outdated",
					"starts at",
					"stops after",
				},
			),
			cliui.JSONFormat(),
		)
	)
	client := new(codersdk.Client)
	cmd := &clibase.Cmd{
		Annotations: workspaceCommand,
		Use:         "list",
		Short:       "List workspaces",
		Aliases:     []string{"ls"},
		Middleware: clibase.Chain(
			clibase.RequireNArgs(0),
			r.InitClient(client),
		),
		Handler: func(inv *clibase.Invocation) error {
			res, err := queryConvertWorkspaces(inv.Context(), client, filter.Filter(), workspaceListRowFromWorkspace)
			if err != nil {
				return err
			}

			if len(res) == 0 {
				pretty.Fprintf(inv.Stderr, cliui.DefaultStyles.Prompt, "No workspaces found! Create one:\n")
				_, _ = fmt.Fprintln(inv.Stderr)
				_, _ = fmt.Fprintln(inv.Stderr, "  "+pretty.Sprint(cliui.DefaultStyles.Code, "coder create <name>"))
				_, _ = fmt.Fprintln(inv.Stderr)
				return nil
			}

			out, err := formatter.Format(inv.Context(), res)
			if err != nil {
				return err
			}

			_, err = fmt.Fprintln(inv.Stdout, out)
			return err
		},
	}
	filter.AttachOptions(&cmd.Options)
	formatter.AttachOptions(&cmd.Options)
	return cmd
}

// queryConvertWorkspaces is a helper function for converting
// codersdk.Workspaces to a different type.
// It's used by the list command to convert workspaces to
// workspaceListRow, and by the schedule command to
// convert workspaces to scheduleListRow.
func queryConvertWorkspaces[T any](ctx context.Context, client *codersdk.Client, filter codersdk.WorkspaceFilter, convertF func(time.Time, codersdk.Workspace) T) ([]T, error) {
	var empty []T
	workspaces, err := client.Workspaces(ctx, filter)
	if err != nil {
		return empty, xerrors.Errorf("query workspaces: %w", err)
	}
	converted := make([]T, len(workspaces.Workspaces))
	for i, workspace := range workspaces.Workspaces {
		converted[i] = convertF(time.Now(), workspace)
	}
	return converted, nil
}
