package list

import (
	"fmt"
	"net/http"
	"time"

	"github.com/MakeNowJust/heredoc"

	"github.com/cli/cli/v2/api"
	"github.com/cli/cli/v2/internal/ghrepo"
	"github.com/cli/cli/v2/internal/tableprinter"
	"github.com/cli/cli/v2/pkg/cmd/run/shared"
	workflowShared "github.com/cli/cli/v2/pkg/cmd/workflow/shared"
	"github.com/cli/cli/v2/pkg/cmdutil"
	"github.com/cli/cli/v2/pkg/iostreams"
	"github.com/spf13/cobra"
)

const (
	defaultLimit = 20
)

type ListOptions struct {
	IO         *iostreams.IOStreams
	HttpClient func() (*http.Client, error)
	BaseRepo   func() (ghrepo.Interface, error)
	Prompter   iprompter

	Exporter cmdutil.Exporter

	Limit            int
	WorkflowSelector string
	Branch           string
	Actor            string
	Status           string
	Event            string
	Created          string
	Commit           string
	All              bool

	now time.Time
}

type iprompter interface {
	Select(string, string, []string) (int, error)
}

func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
	opts := &ListOptions{
		IO:         f.IOStreams,
		HttpClient: f.HttpClient,
		Prompter:   f.Prompter,
		now:        time.Now(),
	}

	cmd := &cobra.Command{
		Use:   "list",
		Short: "List recent workflow runs",
		Long: heredoc.Docf(`
			List recent workflow runs.

			Note that providing the %[1]sworkflow_name%[1]s to the %[1]s-w%[1]s flag will not fetch disabled workflows.
			Also pass the %[1]s-a%[1]s flag to fetch disabled workflow runs using the %[1]sworkflow_name%[1]s and the %[1]s-w%[1]s flag.

			Runs created by organization and enterprise ruleset workflows will not display a workflow name due to GitHub API limitations.

			To see runs associated with a pull request, users should run %[1]sgh pr checks%[1]s.
		`, "`"),
		Aliases: []string{"ls"},
		Args:    cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			// support `-R, --repo` override
			opts.BaseRepo = f.BaseRepo

			if opts.Limit < 1 {
				return cmdutil.FlagErrorf("invalid limit: %v", opts.Limit)
			}

			if runF != nil {
				return runF(opts)
			}

			return listRun(opts)
		},
	}

	cmd.Flags().IntVarP(&opts.Limit, "limit", "L", defaultLimit, "Maximum number of runs to fetch")
	cmd.Flags().StringVarP(&opts.WorkflowSelector, "workflow", "w", "", "Filter runs by workflow")
	cmd.Flags().StringVarP(&opts.Branch, "branch", "b", "", "Filter runs by branch")
	cmd.Flags().StringVarP(&opts.Actor, "user", "u", "", "Filter runs by user who triggered the run")
	cmd.Flags().StringVarP(&opts.Event, "event", "e", "", "Filter runs by which `event` triggered the run")
	cmd.Flags().StringVarP(&opts.Created, "created", "", "", "Filter runs by the `date` it was created")
	cmd.Flags().StringVarP(&opts.Commit, "commit", "c", "", "Filter runs by the `SHA` of the commit")
	cmd.Flags().BoolVarP(&opts.All, "all", "a", false, "Include disabled workflows")
	cmdutil.StringEnumFlag(cmd, &opts.Status, "status", "s", "", shared.AllStatuses, "Filter runs by status")
	cmdutil.AddJSONFlags(cmd, &opts.Exporter, shared.RunFields)

	_ = cmdutil.RegisterBranchCompletionFlags(f.GitClient, cmd, "branch")

	return cmd
}

func listRun(opts *ListOptions) error {
	baseRepo, err := opts.BaseRepo()
	if err != nil {
		return fmt.Errorf("failed to determine base repo: %w", err)
	}

	c, err := opts.HttpClient()
	if err != nil {
		return fmt.Errorf("failed to create http client: %w", err)
	}
	client := api.NewClientFromHTTP(c)

	filters := &shared.FilterOptions{
		Branch:  opts.Branch,
		Actor:   opts.Actor,
		Status:  opts.Status,
		Event:   opts.Event,
		Created: opts.Created,
		Commit:  opts.Commit,
	}

	opts.IO.StartProgressIndicator()
	defer opts.IO.StopProgressIndicator()

	if opts.WorkflowSelector != "" {
		// initially the workflow state is limited to 'active'
		states := []workflowShared.WorkflowState{workflowShared.Active}
		if opts.All {
			// the all flag tells us to add the remaining workflow states
			// note: this will be incomplete if more workflow states are added to `workflowShared`
			states = append(states, workflowShared.DisabledManually, workflowShared.DisabledInactivity)
		}
		if workflow, err := workflowShared.ResolveWorkflow(opts.Prompter, opts.IO, client, baseRepo, false, opts.WorkflowSelector, states); err == nil {
			filters.WorkflowID = workflow.ID
			filters.WorkflowName = workflow.Name
		} else {
			return err
		}
	}
	runsResult, err := shared.GetRuns(client, baseRepo, filters, opts.Limit)
	opts.IO.StopProgressIndicator()
	if err != nil {
		return fmt.Errorf("failed to get runs: %w", err)
	}
	runs := runsResult.WorkflowRuns
	if len(runs) == 0 && opts.Exporter == nil {
		return cmdutil.NewNoResultsError("no runs found")
	}

	if err := opts.IO.StartPager(); err == nil {
		defer opts.IO.StopPager()
	} else {
		fmt.Fprintf(opts.IO.ErrOut, "failed to start pager: %v\n", err)
	}

	if opts.Exporter != nil {
		return opts.Exporter.Write(opts.IO, runs)
	}

	tp := tableprinter.New(opts.IO, tableprinter.WithHeader("STATUS", "TITLE", "WORKFLOW", "BRANCH", "EVENT", "ID", "ELAPSED", "AGE"))

	cs := opts.IO.ColorScheme()

	for _, run := range runs {
		if tp.IsTTY() {
			symbol, symbolColor := shared.Symbol(cs, run.Status, run.Conclusion)
			tp.AddField(symbol, tableprinter.WithColor(symbolColor))
		} else {
			tp.AddField(string(run.Status))
			tp.AddField(string(run.Conclusion))
		}
		tp.AddField(run.Title(), tableprinter.WithColor(cs.Bold))
		tp.AddField(run.WorkflowName())
		tp.AddField(run.HeadBranch, tableprinter.WithColor(cs.Bold))
		tp.AddField(string(run.Event))
		tp.AddField(fmt.Sprintf("%d", run.ID), tableprinter.WithColor(cs.Cyan))
		tp.AddField(run.Duration(opts.now).String())
		tp.AddTimeField(opts.now, run.StartedTime(), cs.Muted)
		tp.EndRow()
	}

	if err := tp.Render(); err != nil {
		return err
	}

	return nil
}
