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

package cmd

import (
	"context"
	"fmt"

	"forgejo.org/models/db"
	repo_model "forgejo.org/models/repo"
	"forgejo.org/modules/git"
	"forgejo.org/modules/gitrepo"
	"forgejo.org/modules/log"
	repo_module "forgejo.org/modules/repository"

	"github.com/urfave/cli/v3"
)

// CmdAdmin represents the available admin sub-command.
func cmdAdmin() *cli.Command {
	return &cli.Command{
		Name:  "admin",
		Usage: "Perform common administrative operations",
		Commands: []*cli.Command{
			subcmdUser(),
			subcmdRepoSyncReleases(),
			subcmdRegenerate(),
			subcmdAuth(),
			subcmdSendMail(),
		},
	}
}

func subcmdRepoSyncReleases() *cli.Command {
	return &cli.Command{
		Name:   "repo-sync-releases",
		Usage:  "Synchronize repository releases with tags",
		Before: noDanglingArgs,
		Action: runRepoSyncReleases,
	}
}

func subcmdRegenerate() *cli.Command {
	return &cli.Command{
		Name:  "regenerate",
		Usage: "Regenerate specific files",
		Commands: []*cli.Command{
			microcmdRegenHooks,
			microcmdRegenKeys,
		},
	}
}

func subcmdAuth() *cli.Command {
	return &cli.Command{
		Name:  "auth",
		Usage: "Modify external auth providers",
		Commands: []*cli.Command{
			microcmdAuthAddOauth(),
			microcmdAuthUpdateOauth(),
			microcmdAuthAddLdapBindDn(),
			microcmdAuthUpdateLdapBindDn(),
			microcmdAuthAddLdapSimpleAuth(),
			microcmdAuthUpdateLdapSimpleAuth(),
			microcmdAuthAddPAM(),
			microcmdAuthUpdatePAM(),
			microcmdAuthAddSMTP(),
			microcmdAuthUpdateSMTP(),
			microcmdAuthList(),
			microcmdAuthDelete(),
		},
	}
}

func subcmdSendMail() *cli.Command {
	return &cli.Command{
		Name:   "sendmail",
		Usage:  "Send a message to all users",
		Before: noDanglingArgs,
		Action: runSendMail,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:  "title",
				Usage: `a title of a message`,
				Value: "",
			},
			&cli.StringFlag{
				Name:  "content",
				Usage: "a content of a message",
				Value: "",
			},
			&cli.BoolFlag{
				Name:    "force",
				Aliases: []string{"f"},
				Usage:   "A flag to bypass a confirmation step",
			},
		},
	}
}

func idFlag() *cli.Int64Flag {
	return &cli.Int64Flag{
		Name:  "id",
		Usage: "ID of authentication source",
	}
}

func runRepoSyncReleases(ctx context.Context, c *cli.Command) error {
	ctx, cancel := installSignals(ctx)
	defer cancel()

	if err := initDB(ctx); err != nil {
		return err
	}

	if err := git.InitSimple(ctx); err != nil {
		return err
	}

	log.Trace("Synchronizing repository releases (this may take a while)")
	for page := 1; ; page++ {
		repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{
			ListOptions: db.ListOptions{
				PageSize: repo_model.RepositoryListDefaultPageSize,
				Page:     page,
			},
			Private: true,
		})
		if err != nil {
			return fmt.Errorf("SearchRepositoryByName: %w", err)
		}
		if len(repos) == 0 {
			break
		}
		log.Trace("Processing next %d repos of %d", len(repos), count)
		for _, repo := range repos {
			log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath())
			gitRepo, err := gitrepo.OpenRepository(ctx, repo)
			if err != nil {
				log.Warn("OpenRepository: %v", err)
				continue
			}

			oldnum, err := getReleaseCount(ctx, repo.ID)
			if err != nil {
				log.Warn(" GetReleaseCountByRepoID: %v", err)
			}
			log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum)

			if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
				log.Warn(" SyncReleasesWithTags: %v", err)
				gitRepo.Close()
				continue
			}

			count, err = getReleaseCount(ctx, repo.ID)
			if err != nil {
				log.Warn(" GetReleaseCountByRepoID: %v", err)
				gitRepo.Close()
				continue
			}

			log.Trace(" repo %s releases synchronized to tags: from %d to %d",
				repo.FullName(), oldnum, count)
			gitRepo.Close()
		}
	}

	return nil
}

func getReleaseCount(ctx context.Context, id int64) (int64, error) {
	return db.Count[repo_model.Release](
		ctx,
		repo_model.FindReleasesOptions{
			RepoID:      id,
			IncludeTags: true,
		},
	)
}
