// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package repos

import (
	stdctx "context"
	"fmt"
	"net/http"

	"code.gitea.io/tea/cmd/flags"
	"code.gitea.io/tea/modules/context"
	"code.gitea.io/tea/modules/print"

	"code.gitea.io/sdk/gitea"
	"github.com/urfave/cli/v3"
)

var repoFieldsFlag = flags.FieldsFlag(print.RepoFields, []string{
	"owner", "name", "type", "ssh",
})

// CmdReposListFlags contains all flags needed for repo listing
var CmdReposListFlags = append([]cli.Flag{
	&cli.BoolFlag{
		Name:     "watched",
		Aliases:  []string{"w"},
		Required: false,
		Usage:    "List your watched repos instead",
	},
	&cli.BoolFlag{
		Name:     "starred",
		Aliases:  []string{"s"},
		Required: false,
		Usage:    "List your starred repos instead",
	},
	&cli.StringFlag{
		Name:     "owner",
		Aliases:  []string{"O"},
		Required: false,
		Usage:    "List repos of a specific owner (org or user)",
	},
	repoFieldsFlag,
	&typeFilterFlag,
	&flags.PaginationPageFlag,
	&flags.PaginationLimitFlag,
}, flags.LoginOutputFlags...)

// CmdReposList represents a sub command of repos to list them
var CmdReposList = cli.Command{
	Name:        "list",
	Aliases:     []string{"ls"},
	Usage:       "List repositories you have access to",
	Description: "List repositories you have access to",
	Action:      RunReposList,
	Flags:       CmdReposListFlags,
}

// RunReposList list repositories
func RunReposList(_ stdctx.Context, cmd *cli.Command) error {
	teaCmd, err := context.InitCommand(cmd)
	if err != nil {
		return err
	}
	client := teaCmd.Login.Client()

	typeFilter, err := getTypeFilter(cmd)
	if err != nil {
		return err
	}

	var rps []*gitea.Repository
	if owner := teaCmd.String("owner"); owner != "" {
		var err error
		_, resp, orgErr := client.GetOrg(owner)
		if orgErr != nil {
			if resp == nil || resp.StatusCode != http.StatusNotFound {
				return fmt.Errorf("could not find owner: %w", orgErr)
			}
			// not an org, treat as user
			rps, _, err = client.ListUserRepos(owner, gitea.ListReposOptions{
				ListOptions: flags.GetListOptions(cmd),
			})
		} else {
			rps, _, err = client.ListOrgRepos(owner, gitea.ListOrgReposOptions{
				ListOptions: flags.GetListOptions(cmd),
			})
		}
		if err != nil {
			return err
		}
	} else if teaCmd.Bool("starred") {
		user, _, err := client.GetMyUserInfo()
		if err != nil {
			return err
		}
		rps, _, err = client.SearchRepos(gitea.SearchRepoOptions{
			ListOptions:     flags.GetListOptions(cmd),
			StarredByUserID: user.ID,
		})
		if err != nil {
			return err
		}
	} else if teaCmd.Bool("watched") {
		// GetMyWatchedRepos doesn't expose server-side pagination,
		// so we implement client-side pagination as a workaround
		allRepos, _, err := client.GetMyWatchedRepos()
		if err != nil {
			return err
		}
		rps = paginateRepos(allRepos, flags.GetListOptions(cmd))
	} else {
		var err error
		rps, _, err = client.ListMyRepos(gitea.ListReposOptions{
			ListOptions: flags.GetListOptions(cmd),
		})
		if err != nil {
			return err
		}
	}

	reposFiltered := rps
	if typeFilter != gitea.RepoTypeNone {
		reposFiltered = filterReposByType(rps, typeFilter)
	}

	fields, err := repoFieldsFlag.GetValues(cmd)
	if err != nil {
		return err
	}

	return print.ReposList(reposFiltered, teaCmd.Output, fields)
}

func filterReposByType(repos []*gitea.Repository, t gitea.RepoType) []*gitea.Repository {
	var filtered []*gitea.Repository
	for _, r := range repos {
		switch t {
		case gitea.RepoTypeFork:
			if !r.Fork {
				continue
			}
		case gitea.RepoTypeMirror:
			if !r.Mirror {
				continue
			}
		case gitea.RepoTypeSource:
			if r.Fork || r.Mirror {
				continue
			}
		}

		filtered = append(filtered, r)
	}
	return filtered
}

// paginateRepos implements client-side pagination for repositories.
// This is a workaround for API endpoints that don't support server-side pagination.
func paginateRepos(repos []*gitea.Repository, opts gitea.ListOptions) []*gitea.Repository {
	if len(repos) == 0 {
		return repos
	}

	pageSize := opts.PageSize
	if pageSize <= 0 {
		pageSize = flags.PaginationLimitFlag.Value
	}

	page := opts.Page
	if page < 1 {
		page = 1
	}

	start := (page - 1) * pageSize
	end := start + pageSize

	if start >= len(repos) {
		return []*gitea.Repository{}
	}

	if end > len(repos) {
		end = len(repos)
	}

	return repos[start:end]
}
