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

package org

import (
	"net/http"

	git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
	api "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)

// toAPIOrgBranchProtection converts model to API response
func toAPIOrgBranchProtection(ctx *context.APIContext, rule *git_model.OrgProtectedBranch) *api.OrgBranchProtection {
	orgID := rule.OrgID
	teams, err := organization.FindOrgTeams(ctx, orgID)
	if err != nil {
		teams = nil
	}

	teamNamesByID := make(map[int64]string, len(teams))
	for _, t := range teams {
		teamNamesByID[t.ID] = t.Name
	}

	resolveTeamNames := func(ids []int64) []string {
		names := make([]string, 0, len(ids))
		for _, id := range ids {
			if name, ok := teamNamesByID[id]; ok {
				names = append(names, name)
			}
		}
		return names
	}

	return &api.OrgBranchProtection{
		ID:                            rule.ID,
		OrgID:                         rule.OrgID,
		RuleName:                      rule.RuleName,
		Priority:                      rule.Priority,
		EnablePush:                    rule.CanPush,
		EnablePushWhitelist:           rule.EnableWhitelist,
		PushWhitelistTeams:            resolveTeamNames(rule.WhitelistTeamIDs),
		EnableForcePush:               rule.CanForcePush,
		EnableForcePushAllowlist:      rule.EnableForcePushAllowlist,
		ForcePushAllowlistTeams:       resolveTeamNames(rule.ForcePushAllowlistTeamIDs),
		EnableMergeWhitelist:          rule.EnableMergeWhitelist,
		MergeWhitelistTeams:           resolveTeamNames(rule.MergeWhitelistTeamIDs),
		EnableStatusCheck:             rule.EnableStatusCheck,
		StatusCheckContexts:           rule.StatusCheckContexts,
		RequiredApprovals:             rule.RequiredApprovals,
		EnableApprovalsWhitelist:      rule.EnableApprovalsWhitelist,
		ApprovalsWhitelistTeams:       resolveTeamNames(rule.ApprovalsWhitelistTeamIDs),
		BlockOnRejectedReviews:        rule.BlockOnRejectedReviews,
		BlockOnOfficialReviewRequests: rule.BlockOnOfficialReviewRequests,
		BlockOnOutdatedBranch:         rule.BlockOnOutdatedBranch,
		DismissStaleApprovals:         rule.DismissStaleApprovals,
		IgnoreStaleApprovals:          rule.IgnoreStaleApprovals,
		RequireSignedCommits:          rule.RequireSignedCommits,
		ProtectedFilePatterns:         rule.ProtectedFilePatterns,
		UnprotectedFilePatterns:       rule.UnprotectedFilePatterns,
		BlockAdminMergeOverride:       rule.BlockAdminMergeOverride,
		Created:                       rule.CreatedUnix.AsTime(),
		Updated:                       rule.UpdatedUnix.AsTime(),
	}
}

// resolveTeamIDs converts team names to IDs for the given org, returning 422 on unknown team
func resolveTeamIDs(ctx *context.APIContext, orgID int64, names []string) ([]int64, bool) {
	if len(names) == 0 {
		return nil, true
	}
	ids, err := organization.GetTeamIDsByNames(ctx, orgID, names, false)
	if err != nil {
		if organization.IsErrTeamNotExist(err) {
			ctx.APIError(http.StatusUnprocessableEntity, err)
		} else {
			ctx.APIErrorInternal(err)
		}
		return nil, false
	}
	return ids, true
}

// ListOrgBranchProtections list org-level branch protection rules
func ListOrgBranchProtections(ctx *context.APIContext) {
	// swagger:operation GET /orgs/{org}/branch_protections organization orgListBranchProtections
	// ---
	// summary: List an organization's branch protection rules
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// responses:
	//   "200":
	//     "$ref": "#/responses/OrgBranchProtectionList"
	//   "404":
	//     "$ref": "#/responses/notFound"

	rules, err := git_model.FindOrgProtectedBranchRules(ctx, ctx.Org.Organization.ID)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}
	apiRules := make([]*api.OrgBranchProtection, len(rules))
	for i, rule := range rules {
		apiRules[i] = toAPIOrgBranchProtection(ctx, rule)
	}
	ctx.JSON(http.StatusOK, apiRules)
}

// GetOrgBranchProtection get a specific org-level branch protection rule
func GetOrgBranchProtection(ctx *context.APIContext) {
	// swagger:operation GET /orgs/{org}/branch_protections/{name} organization orgGetBranchProtection
	// ---
	// summary: Get a specific org-level branch protection rule
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// - name: name
	//   in: path
	//   description: name of the branch protection rule
	//   type: string
	//   required: true
	// responses:
	//   "200":
	//     "$ref": "#/responses/OrgBranchProtection"
	//   "404":
	//     "$ref": "#/responses/notFound"

	ruleName := ctx.PathParam("*")
	rule, err := git_model.GetOrgProtectedBranchByName(ctx, ctx.Org.Organization.ID, ruleName)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}
	if rule == nil {
		ctx.APIErrorNotFound()
		return
	}
	ctx.JSON(http.StatusOK, toAPIOrgBranchProtection(ctx, rule))
}

// CreateOrgBranchProtection create an org-level branch protection rule
func CreateOrgBranchProtection(ctx *context.APIContext) {
	// swagger:operation POST /orgs/{org}/branch_protections organization orgCreateBranchProtection
	// ---
	// summary: Create an org-level branch protection rule
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// - name: body
	//   in: body
	//   schema:
	//     "$ref": "#/definitions/CreateOrgBranchProtectionOption"
	// responses:
	//   "201":
	//     "$ref": "#/responses/OrgBranchProtection"
	//   "403":
	//     "$ref": "#/responses/forbidden"
	//   "404":
	//     "$ref": "#/responses/notFound"
	//   "422":
	//     "$ref": "#/responses/validationError"

	form := web.GetForm(ctx).(*api.CreateOrgBranchProtectionOption)
	orgID := ctx.Org.Organization.ID

	existing, err := git_model.GetOrgProtectedBranchByName(ctx, orgID, form.RuleName)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}
	if existing != nil {
		ctx.APIError(http.StatusForbidden, "org branch protection rule already exists for this pattern")
		return
	}

	pushTeams, ok := resolveTeamIDs(ctx, orgID, form.PushWhitelistTeams)
	if !ok {
		return
	}
	forcePushTeams, ok := resolveTeamIDs(ctx, orgID, form.ForcePushAllowlistTeams)
	if !ok {
		return
	}
	mergeTeams, ok := resolveTeamIDs(ctx, orgID, form.MergeWhitelistTeams)
	if !ok {
		return
	}
	approvalsTeams, ok := resolveTeamIDs(ctx, orgID, form.ApprovalsWhitelistTeams)
	if !ok {
		return
	}

	rule := &git_model.OrgProtectedBranch{
		OrgID:                         orgID,
		RuleName:                      form.RuleName,
		Priority:                      form.Priority,
		CanPush:                       form.EnablePush,
		EnableWhitelist:               form.EnablePush && form.EnablePushWhitelist,
		WhitelistTeamIDs:              pushTeams,
		CanForcePush:                  form.EnablePush && form.EnableForcePush,
		EnableForcePushAllowlist:      form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist,
		ForcePushAllowlistTeamIDs:     forcePushTeams,
		EnableMergeWhitelist:          form.EnableMergeWhitelist,
		MergeWhitelistTeamIDs:         mergeTeams,
		EnableStatusCheck:             form.EnableStatusCheck,
		StatusCheckContexts:           form.StatusCheckContexts,
		RequiredApprovals:             form.RequiredApprovals,
		EnableApprovalsWhitelist:      form.EnableApprovalsWhitelist,
		ApprovalsWhitelistTeamIDs:     approvalsTeams,
		BlockOnRejectedReviews:        form.BlockOnRejectedReviews,
		BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
		BlockOnOutdatedBranch:         form.BlockOnOutdatedBranch,
		DismissStaleApprovals:         form.DismissStaleApprovals,
		IgnoreStaleApprovals:          form.IgnoreStaleApprovals,
		RequireSignedCommits:          form.RequireSignedCommits,
		ProtectedFilePatterns:         form.ProtectedFilePatterns,
		UnprotectedFilePatterns:       form.UnprotectedFilePatterns,
		BlockAdminMergeOverride:       form.BlockAdminMergeOverride,
	}

	if err := git_model.CreateOrgProtectedBranch(ctx, rule); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusCreated, toAPIOrgBranchProtection(ctx, rule))
}

// EditOrgBranchProtection edit an org-level branch protection rule
func EditOrgBranchProtection(ctx *context.APIContext) {
	// swagger:operation PATCH /orgs/{org}/branch_protections/{name} organization orgEditBranchProtection
	// ---
	// summary: Edit an org-level branch protection rule. Only fields that are set will be changed
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// - name: name
	//   in: path
	//   description: name of the branch protection rule
	//   type: string
	//   required: true
	// - name: body
	//   in: body
	//   schema:
	//     "$ref": "#/definitions/EditOrgBranchProtectionOption"
	// responses:
	//   "200":
	//     "$ref": "#/responses/OrgBranchProtection"
	//   "404":
	//     "$ref": "#/responses/notFound"
	//   "422":
	//     "$ref": "#/responses/validationError"

	form := web.GetForm(ctx).(*api.EditOrgBranchProtectionOption)
	orgID := ctx.Org.Organization.ID
	ruleName := ctx.PathParam("*")

	rule, err := git_model.GetOrgProtectedBranchByName(ctx, orgID, ruleName)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}
	if rule == nil {
		ctx.APIErrorNotFound()
		return
	}

	if form.Priority != nil {
		rule.Priority = *form.Priority
	}
	if form.EnablePush != nil {
		rule.CanPush = *form.EnablePush
	}
	if form.EnablePushWhitelist != nil {
		rule.EnableWhitelist = *form.EnablePushWhitelist
	}
	if form.PushWhitelistTeams != nil {
		ids, ok := resolveTeamIDs(ctx, orgID, form.PushWhitelistTeams)
		if !ok {
			return
		}
		rule.WhitelistTeamIDs = ids
	}
	if form.EnableForcePush != nil {
		rule.CanForcePush = *form.EnableForcePush
	}
	if form.EnableForcePushAllowlist != nil {
		rule.EnableForcePushAllowlist = *form.EnableForcePushAllowlist
	}
	if form.ForcePushAllowlistTeams != nil {
		ids, ok := resolveTeamIDs(ctx, orgID, form.ForcePushAllowlistTeams)
		if !ok {
			return
		}
		rule.ForcePushAllowlistTeamIDs = ids
	}
	if form.EnableMergeWhitelist != nil {
		rule.EnableMergeWhitelist = *form.EnableMergeWhitelist
	}
	if form.MergeWhitelistTeams != nil {
		ids, ok := resolveTeamIDs(ctx, orgID, form.MergeWhitelistTeams)
		if !ok {
			return
		}
		rule.MergeWhitelistTeamIDs = ids
	}
	if form.EnableStatusCheck != nil {
		rule.EnableStatusCheck = *form.EnableStatusCheck
	}
	if form.StatusCheckContexts != nil {
		rule.StatusCheckContexts = form.StatusCheckContexts
	}
	if form.RequiredApprovals != nil {
		rule.RequiredApprovals = *form.RequiredApprovals
	}
	if form.EnableApprovalsWhitelist != nil {
		rule.EnableApprovalsWhitelist = *form.EnableApprovalsWhitelist
	}
	if form.ApprovalsWhitelistTeams != nil {
		ids, ok := resolveTeamIDs(ctx, orgID, form.ApprovalsWhitelistTeams)
		if !ok {
			return
		}
		rule.ApprovalsWhitelistTeamIDs = ids
	}
	if form.BlockOnRejectedReviews != nil {
		rule.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
	}
	if form.BlockOnOfficialReviewRequests != nil {
		rule.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
	}
	if form.BlockOnOutdatedBranch != nil {
		rule.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
	}
	if form.DismissStaleApprovals != nil {
		rule.DismissStaleApprovals = *form.DismissStaleApprovals
	}
	if form.IgnoreStaleApprovals != nil {
		rule.IgnoreStaleApprovals = *form.IgnoreStaleApprovals
	}
	if form.RequireSignedCommits != nil {
		rule.RequireSignedCommits = *form.RequireSignedCommits
	}
	if form.ProtectedFilePatterns != nil {
		rule.ProtectedFilePatterns = *form.ProtectedFilePatterns
	}
	if form.UnprotectedFilePatterns != nil {
		rule.UnprotectedFilePatterns = *form.UnprotectedFilePatterns
	}
	if form.BlockAdminMergeOverride != nil {
		rule.BlockAdminMergeOverride = *form.BlockAdminMergeOverride
	}

	if err := git_model.UpdateOrgProtectedBranch(ctx, rule); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusOK, toAPIOrgBranchProtection(ctx, rule))
}

// DeleteOrgBranchProtection delete an org-level branch protection rule
func DeleteOrgBranchProtection(ctx *context.APIContext) {
	// swagger:operation DELETE /orgs/{org}/branch_protections/{name} organization orgDeleteBranchProtection
	// ---
	// summary: Delete an org-level branch protection rule
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// - name: name
	//   in: path
	//   description: name of the branch protection rule
	//   type: string
	//   required: true
	// responses:
	//   "204":
	//     "$ref": "#/responses/empty"
	//   "404":
	//     "$ref": "#/responses/notFound"

	orgID := ctx.Org.Organization.ID
	ruleName := ctx.PathParam("*")

	rule, err := git_model.GetOrgProtectedBranchByName(ctx, orgID, ruleName)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}
	if rule == nil {
		ctx.APIErrorNotFound()
		return
	}

	if err := git_model.DeleteOrgProtectedBranch(ctx, orgID, rule.ID); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.Status(http.StatusNoContent)
}
