package list

import (
	"fmt"
	"strconv"

	"github.com/MakeNowJust/heredoc"
	"github.com/cli/cli/v2/internal/tableprinter"
	"github.com/cli/cli/v2/pkg/cmd/project/shared/client"
	"github.com/cli/cli/v2/pkg/cmd/project/shared/format"
	"github.com/cli/cli/v2/pkg/cmd/project/shared/queries"
	"github.com/cli/cli/v2/pkg/cmdutil"
	"github.com/cli/cli/v2/pkg/iostreams"
	"github.com/spf13/cobra"
)

type listOpts struct {
	limit    int
	web      bool
	owner    string
	closed   bool
	exporter cmdutil.Exporter
}

type listConfig struct {
	client    *queries.Client
	opts      listOpts
	URLOpener func(string) error
	io        *iostreams.IOStreams
}

func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.Command {
	opts := listOpts{}
	listCmd := &cobra.Command{
		Use:   "list",
		Short: "List the projects for an owner",
		Example: heredoc.Doc(`
			# List the current user's projects
			$ gh project list

			# List the projects for org github including closed projects
			$ gh project list --owner github --closed
		`),
		Aliases: []string{"ls"},
		RunE: func(cmd *cobra.Command, args []string) error {
			client, err := client.New(f)
			if err != nil {
				return err
			}

			URLOpener := func(url string) error {
				return f.Browser.Browse(url)
			}

			config := listConfig{
				client:    client,
				opts:      opts,
				URLOpener: URLOpener,
				io:        f.IOStreams,
			}

			// allow testing of the command without actually running it
			if runF != nil {
				return runF(config)
			}
			return runList(config)
		},
	}
	listCmd.Flags().StringVar(&opts.owner, "owner", "", "Login of the owner")
	listCmd.Flags().BoolVarP(&opts.closed, "closed", "", false, "Include closed projects")
	listCmd.Flags().BoolVarP(&opts.web, "web", "w", false, "Open projects list in the browser")
	cmdutil.AddFormatFlags(listCmd, &opts.exporter)
	listCmd.Flags().IntVarP(&opts.limit, "limit", "L", queries.LimitDefault, "Maximum number of projects to fetch")

	return listCmd
}

func runList(config listConfig) error {
	if config.opts.web {
		url, err := buildURL(config)
		if err != nil {
			return err
		}

		if err := config.URLOpener(url); err != nil {
			return err
		}
		return nil
	}

	if config.opts.owner == "" {
		config.opts.owner = "@me"
	}
	canPrompt := config.io.CanPrompt()
	owner, err := config.client.NewOwner(canPrompt, config.opts.owner)
	if err != nil {
		return err
	}

	projects, err := config.client.Projects(config.opts.owner, owner.Type, config.opts.limit, false)
	if err != nil {
		return err
	}
	projects = filterProjects(projects, config)

	if config.opts.exporter != nil {
		return config.opts.exporter.Write(config.io, projects)
	}

	return printResults(config, projects, owner.Login)
}

// TODO: support non-github.com hostnames
func buildURL(config listConfig) (string, error) {
	var url string
	if config.opts.owner == "@me" || config.opts.owner == "" {
		owner, err := config.client.ViewerLoginName()
		if err != nil {
			return "", err
		}
		url = fmt.Sprintf("https://github.com/users/%s/projects", owner)
	} else {
		_, ownerType, err := config.client.OwnerIDAndType(config.opts.owner)
		if err != nil {
			return "", err
		}

		if ownerType == queries.UserOwner {
			url = fmt.Sprintf("https://github.com/users/%s/projects", config.opts.owner)
		} else {
			url = fmt.Sprintf("https://github.com/orgs/%s/projects", config.opts.owner)
		}
	}

	if config.opts.closed {
		return url + "?query=is%3Aclosed", nil
	}
	return url, nil
}

func filterProjects(nodes queries.Projects, config listConfig) queries.Projects {
	filtered := queries.Projects{
		Nodes:      make([]queries.Project, 0, len(nodes.Nodes)),
		TotalCount: nodes.TotalCount,
	}
	for _, project := range nodes.Nodes {
		if !config.opts.closed && project.Closed {
			continue
		}
		filtered.Nodes = append(filtered.Nodes, project)
	}
	return filtered
}

func printResults(config listConfig, projects queries.Projects, owner string) error {
	if len(projects.Nodes) == 0 {
		return cmdutil.NewNoResultsError(fmt.Sprintf("No projects found for %s", owner))
	}

	tp := tableprinter.New(config.io, tableprinter.WithHeader("Number", "Title", "State", "ID"))

	cs := config.io.ColorScheme()
	for _, p := range projects.Nodes {
		tp.AddField(
			strconv.Itoa(int(p.Number)),
			tableprinter.WithTruncate(nil),
		)
		tp.AddField(p.Title)
		tp.AddField(
			format.ProjectState(p),
			tableprinter.WithColor(cs.ColorFromString(format.ColorForProjectState(p))),
		)
		tp.AddField(p.ID, tableprinter.WithTruncate(nil))
		tp.EndRow()
	}

	return tp.Render()
}
