package repo

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

	git_model "code.gitea.io/gitea/models/git"
	"code.gitea.io/gitea/modules/context"
	gitea_git "code.gitea.io/gitea/modules/git"
	repo_module "code.gitea.io/gitea/modules/repository"
	"code.gitea.io/gitea/modules/util"
	"code.gitea.io/gitea/modules/web"
	"code.gitea.io/gitea/services/convert"
	repo_service "code.gitea.io/gitea/services/repository"
	hat_convert "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/convert"
	hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
	hat_branch_convert "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/convert"

	"code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git"

	"code.gitea.io/gitea/routers/api/v1/utils"
)

func ListBranches(ctx *context.APIContext) {
	var totalNumOfBranches int64
	var apiBranches []*hat_api.Branch

	searchName := ctx.FormString("name")
	listOptions := utils.GetListOptions(ctx)
	if !ctx.Repo.Repository.IsEmpty {
		if ctx.Repo.GitRepo == nil {
			ctx.Error(http.StatusInternalServerError, "Load git repository failed", nil)
			return
		}
		var isDeleted util.OptionalBool
		switch ctx.FormString("state") {
		case "all":
			isDeleted = util.OptionalBoolNone
		case "deleted":
			isDeleted = util.OptionalBoolTrue
		default:
			isDeleted = util.OptionalBoolFalse
		}

		branchOpts := git_model.FindBranchOptions{
			ListOptions:     listOptions,
			RepoID:          ctx.Repo.Repository.ID,
			IsDeletedBranch: isDeleted,
			Keyword:         searchName,
		}

		var err error
		totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts)
		if err != nil {
			ctx.Error(http.StatusInternalServerError, "CountBranches", err)
			return
		}
		if totalNumOfBranches < 2 { // sync branches immediately because non-empty repository should have at least 1 branch
			_, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
			if err != nil {
				ctx.ServerError("SyncRepoBranches", err)
				return
			}
		}

		rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
		if err != nil {
			ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
			return
		}

		branches, err := git_model.FindBranches(ctx, branchOpts)
		if err != nil {
			ctx.Error(http.StatusInternalServerError, "GetBranches", err)
			return
		}

		apiBranches = make([]*hat_api.Branch, 0, len(branches))
		for i := range branches {
			err := branches[i].LoadDeletedBy(ctx)
			if err != nil {
				ctx.Error(http.StatusInternalServerError, "LoadDeletedBy", err)
				return
			}
			c, err := ctx.Repo.GitRepo.GetCommit(branches[i].CommitID)
			if err != nil {
				// Skip if this branch doesn't exist anymore.
				if gitea_git.IsErrNotExist(err) {
					totalNumOfBranches--
					continue
				}
				ctx.Error(http.StatusInternalServerError, "GetCommit", err)
				return
			}

			branchProtection := rules.GetFirstMatched(branches[i].Name)
			apiBranch, err := hat_branch_convert.ToBranch(ctx, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
			if err != nil {
				ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
				return
			}
			apiBranches = append(apiBranches, apiBranch)
		}
	}

	ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
	ctx.SetTotalCountHeader(totalNumOfBranches)
	ctx.RespHeader().Set("Access-Control-Expose-Headers", "X-Total-Count, Link")
	ctx.JSON(http.StatusOK, &apiBranches)
}

func ListBranchesSlice(ctx *context.APIContext) {
	listOptions := utils.GetListOptions(ctx)

	branches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(0, 0)
	if err != nil {
		ctx.Error(http.StatusInternalServerError, "GetBranches", err)
		return
	}

	apiBranches := make([]*git.Branch, len(branches))
	for i, branch := range branches {
		c, err := branch.GetCommit()
		if err != nil {
			ctx.Error(http.StatusInternalServerError, "GetCommit", err)
			return
		}
		branchProtection, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, branch.Name)
		if err != nil {
			ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
			return
		}
		sBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
		if err != nil {
			ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
			return
		}
		gitBranch := &git.Branch{}
		gitBranch.Branch = sBranch
		gitBranch.CommitID = c.ID.String()
		gitBranch.CommitTime = c.Author.When.Format(time.RFC3339)
		gitBranch.DefaultBranch = ctx.Repo.Repository.DefaultBranch
		gitBranch.BranchKind = int(git.OtherBranch)
		apiBranches[i] = gitBranch
		if branchProtection != nil {
			apiBranches[i].BranchKind = int(git.ProtectedBranch)
		}
		if branch.Name == ctx.Repo.Repository.DefaultBranch {
			apiBranches[i].BranchKind = int(git.DefaultBranch)
		}

	}

	sort.Sort(git.SortBranch(apiBranches))
	BranchesSlice := BranchesSliceByProtection(ctx, apiBranches)

	ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
	ctx.RespHeader().Set("X-Total-Count", fmt.Sprintf("%d", totalNumOfBranches))
	ctx.RespHeader().Set("Access-Control-Expose-Headers", "X-Total-Count, Link")
	ctx.JSON(http.StatusOK, &BranchesSlice)
}

func paginate(branchSlice []*git.Branch, skip, pageSize int) []*git.Branch {
	limit := func() int {
		if skip+pageSize > len(branchSlice) {
			return len(branchSlice)
		} else {
			return skip + pageSize
		}
	}
	start := func() int {
		if skip > len(branchSlice) {
			return len(branchSlice)
		} else {
			return skip
		}
	}
	return branchSlice[start():limit()]

}

func BranchesSliceByProtection(ctx *context.APIContext, branchList []*git.Branch) []git.BranchesSlice {
	// group by protection
	sort.Sort(git.SortBranch(branchList))
	branchSlice := make([]git.BranchesSlice, 0)
	i := 0
	var j int
	for {
		if i >= len(branchList) {
			break
		}
		for j = i + 1; j < len(branchList) && (branchList[i].BranchKind == branchList[j].BranchKind); j++ {
		}
		// get the same branches
		sameBranchSlice := branchList[i:j]
		// sort by time
		sort.Sort(git.SortBranchTime(sameBranchSlice))
		branchSlice = append(branchSlice, git.BranchesSlice{
			BranchName: hat_convert.ToBranchType(branchList[i].BranchKind).Name(),
			Branches:   sameBranchSlice,
		})
		i = j
	}
	return branchSlice
}

func RestoreBranch(ctx *context.APIContext) {

	form := web.GetForm(ctx).(*hat_api.RestoreBranchOption)
	branchID := form.BranchID
	branchName := form.BranchName

	deletedBranch, err := git_model.GetDeletedBranchByID(ctx, ctx.Repo.Repository.ID, branchID)
	if err != nil {
		ctx.Error(http.StatusInternalServerError, "GetDeletedBranchByID: %v", err)
		return
	} else if deletedBranch == nil {
		ctx.Error(http.StatusInternalServerError, "RestoreBranch: Can't restore branch '%s', as it does not exist", branchName)
		return
	}

	if err := gitea_git.Push(ctx, ctx.Repo.Repository.RepoPath(), gitea_git.PushOptions{
		Remote: ctx.Repo.Repository.RepoPath(),
		Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, gitea_git.BranchPrefix, deletedBranch.Name),
		Env:    repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
	}); err != nil {
		if strings.Contains(err.Error(), "already exists") {
			ctx.Error(http.StatusInternalServerError, "RestoreBranch: Can't restore branch '%s', since one with same name already exist", deletedBranch.Name)
			return
		}
		ctx.Error(http.StatusInternalServerError, "RestoreBranch: CreateBranch: %v", err)
		return
	}

	if err := repo_service.PushUpdate(
		&repo_module.PushUpdateOptions{
			RefFullName:  gitea_git.RefNameFromBranch(deletedBranch.Name),
			OldCommitID:  gitea_git.EmptySHA,
			NewCommitID:  deletedBranch.CommitID,
			PusherID:     ctx.Doer.ID,
			PusherName:   ctx.Doer.Name,
			RepoUserName: ctx.Repo.Owner.Name,
			RepoName:     ctx.Repo.Repository.Name,
		}); err != nil {
		ctx.Error(http.StatusInternalServerError, "RestoreBranch: Update: %v", err)
		return
	}

	ctx.Status(http.StatusCreated)
}
