// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package repo

import (
	"fmt"
	"strings"
	"time"

	"code.gitea.io/gitea/models"
	"code.gitea.io/gitea/modules/auth"
	"code.gitea.io/gitea/modules/base"
	"code.gitea.io/gitea/modules/context"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"
)

// ProtectedBranch render the page to protect the repository
func ProtectedBranch(ctx *context.Context) {
	ctx.Data["Title"] = ctx.Tr("repo.settings")
	ctx.Data["PageIsSettingsBranches"] = true

	protectedBranches, err := ctx.Repo.Repository.GetProtectedBranches()
	if err != nil {
		ctx.ServerError("GetProtectedBranches", err)
		return
	}
	ctx.Data["ProtectedBranches"] = protectedBranches

	branches := ctx.Data["Branches"].([]string)
	leftBranches := make([]string, 0, len(branches)-len(protectedBranches))
	for _, b := range branches {
		var protected bool
		for _, pb := range protectedBranches {
			if b == pb.BranchName {
				protected = true
				break
			}
		}
		if !protected {
			leftBranches = append(leftBranches, b)
		}
	}

	ctx.Data["LeftBranches"] = leftBranches

	ctx.HTML(200, tplBranches)
}

// ProtectedBranchPost response for protect for a branch of a repository
func ProtectedBranchPost(ctx *context.Context) {
	ctx.Data["Title"] = ctx.Tr("repo.settings")
	ctx.Data["PageIsSettingsBranches"] = true

	repo := ctx.Repo.Repository

	switch ctx.Query("action") {
	case "default_branch":
		if ctx.HasError() {
			ctx.HTML(200, tplBranches)
			return
		}

		branch := ctx.Query("branch")
		if !ctx.Repo.GitRepo.IsBranchExist(branch) {
			ctx.Status(404)
			return
		} else if repo.DefaultBranch != branch {
			repo.DefaultBranch = branch
			if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
				if !git.IsErrUnsupportedVersion(err) {
					ctx.ServerError("SetDefaultBranch", err)
					return
				}
			}
			if err := repo.UpdateDefaultBranch(); err != nil {
				ctx.ServerError("SetDefaultBranch", err)
				return
			}
		}

		log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)

		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
		ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
	default:
		ctx.NotFound("", nil)
	}
}

// SettingsProtectedBranch renders the protected branch setting page
func SettingsProtectedBranch(c *context.Context) {
	branch := c.Params("*")
	if !c.Repo.GitRepo.IsBranchExist(branch) {
		c.NotFound("IsBranchExist", nil)
		return
	}

	c.Data["Title"] = c.Tr("repo.settings.protected_branch") + " - " + branch
	c.Data["PageIsSettingsBranches"] = true

	protectBranch, err := models.GetProtectedBranchBy(c.Repo.Repository.ID, branch)
	if err != nil {
		if !git.IsErrBranchNotExist(err) {
			c.ServerError("GetProtectBranchOfRepoByName", err)
			return
		}
	}

	if protectBranch == nil {
		// No options found, create defaults.
		protectBranch = &models.ProtectedBranch{
			BranchName: branch,
		}
	}

	users, err := c.Repo.Repository.GetReaders()
	if err != nil {
		c.ServerError("Repo.Repository.GetReaders", err)
		return
	}
	c.Data["Users"] = users
	c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistUserIDs), ",")
	c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistUserIDs), ",")
	c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistUserIDs), ",")
	contexts, _ := models.FindRepoRecentCommitStatusContexts(c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts
	for _, context := range protectBranch.StatusCheckContexts {
		var found bool
		for _, ctx := range contexts {
			if ctx == context {
				found = true
				break
			}
		}
		if !found {
			contexts = append(contexts, context)
		}
	}

	c.Data["branch_status_check_contexts"] = contexts
	c.Data["is_context_required"] = func(context string) bool {
		for _, c := range protectBranch.StatusCheckContexts {
			if c == context {
				return true
			}
		}
		return false
	}

	if c.Repo.Owner.IsOrganization() {
		teams, err := c.Repo.Owner.TeamsWithAccessToRepo(c.Repo.Repository.ID, models.AccessModeRead)
		if err != nil {
			c.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
			return
		}
		c.Data["Teams"] = teams
		c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistTeamIDs), ",")
		c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistTeamIDs), ",")
		c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistTeamIDs), ",")
	}

	c.Data["Branch"] = protectBranch
	c.HTML(200, tplProtectedBranch)
}

// SettingsProtectedBranchPost updates the protected branch settings
func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm) {
	branch := ctx.Params("*")
	if !ctx.Repo.GitRepo.IsBranchExist(branch) {
		ctx.NotFound("IsBranchExist", nil)
		return
	}

	protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, branch)
	if err != nil {
		if !git.IsErrBranchNotExist(err) {
			ctx.ServerError("GetProtectBranchOfRepoByName", err)
			return
		}
	}

	if f.Protected {
		if protectBranch == nil {
			// No options found, create defaults.
			protectBranch = &models.ProtectedBranch{
				RepoID:     ctx.Repo.Repository.ID,
				BranchName: branch,
			}
		}
		if f.RequiredApprovals < 0 {
			ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
			ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, branch))
		}

		var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
		switch f.EnablePush {
		case "all":
			protectBranch.CanPush = true
			protectBranch.EnableWhitelist = false
			protectBranch.WhitelistDeployKeys = false
		case "whitelist":
			protectBranch.CanPush = true
			protectBranch.EnableWhitelist = true
			protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys
			if strings.TrimSpace(f.WhitelistUsers) != "" {
				whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ","))
			}
			if strings.TrimSpace(f.WhitelistTeams) != "" {
				whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ","))
			}
		default:
			protectBranch.CanPush = false
			protectBranch.EnableWhitelist = false
			protectBranch.WhitelistDeployKeys = false
		}

		protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
		if f.EnableMergeWhitelist {
			if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
				mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ","))
			}
			if strings.TrimSpace(f.MergeWhitelistTeams) != "" {
				mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ","))
			}
		}

		protectBranch.EnableStatusCheck = f.EnableStatusCheck
		if f.EnableStatusCheck {
			protectBranch.StatusCheckContexts = f.StatusCheckContexts
		} else {
			protectBranch.StatusCheckContexts = nil
		}

		protectBranch.RequiredApprovals = f.RequiredApprovals
		protectBranch.EnableApprovalsWhitelist = f.EnableApprovalsWhitelist
		if f.EnableApprovalsWhitelist {
			if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" {
				approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ","))
			}
			if strings.TrimSpace(f.ApprovalsWhitelistTeams) != "" {
				approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ","))
			}
		}
		protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
		protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
		protectBranch.RequireSignedCommits = f.RequireSignedCommits

		err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
			UserIDs:          whitelistUsers,
			TeamIDs:          whitelistTeams,
			MergeUserIDs:     mergeWhitelistUsers,
			MergeTeamIDs:     mergeWhitelistTeams,
			ApprovalsUserIDs: approvalsWhitelistUsers,
			ApprovalsTeamIDs: approvalsWhitelistTeams,
		})
		if err != nil {
			ctx.ServerError("UpdateProtectBranch", err)
			return
		}
		ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", branch))
		ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, branch))
	} else {
		if protectBranch != nil {
			if err := ctx.Repo.Repository.DeleteProtectedBranch(protectBranch.ID); err != nil {
				ctx.ServerError("DeleteProtectedBranch", err)
				return
			}
		}
		ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", branch))
		ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
	}
}