// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT

package org

import (
	"net/http"

	"code.gitea.io/gitea/models/organization"
	"code.gitea.io/gitea/models/perm"
	access_model "code.gitea.io/gitea/models/perm/access"
	repo_model "code.gitea.io/gitea/models/repo"
	api "code.gitea.io/gitea/modules/structs"
	"code.gitea.io/gitea/modules/web"
	"code.gitea.io/gitea/services/context"
	"code.gitea.io/gitea/services/convert"
	org_service "code.gitea.io/gitea/services/org"
)

// ListPinnedRepos returns the pinned repositories for an organization
func ListPinnedRepos(ctx *context.APIContext) {
	// swagger:operation GET /orgs/{org}/pinned organization orgListPinnedRepos
	// ---
	// summary: List an organization's pinned repositories
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// responses:
	//   "200":
	//     "$ref": "#/responses/OrgPinnedRepoList"
	//   "404":
	//     "$ref": "#/responses/notFound"

	pinnedRepos, err := org_service.GetOrgPinnedReposWithDetails(ctx, ctx.Org.Organization.ID)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	apiPinnedRepos := make([]*api.OrgPinnedRepo, 0, len(pinnedRepos))
	for _, p := range pinnedRepos {
		if p.Repo == nil {
			continue
		}
		apiPinnedRepos = append(apiPinnedRepos, convertOrgPinnedRepo(ctx, p))
	}

	ctx.JSON(http.StatusOK, apiPinnedRepos)
}

// AddPinnedRepo pins a repository to an organization
func AddPinnedRepo(ctx *context.APIContext) {
	// swagger:operation POST /orgs/{org}/pinned organization orgAddPinnedRepo
	// ---
	// summary: Pin a repository to an organization
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// - name: body
	//   in: body
	//   required: true
	//   schema:
	//     "$ref": "#/definitions/AddOrgPinnedRepoOption"
	// responses:
	//   "201":
	//     "$ref": "#/responses/OrgPinnedRepo"
	//   "403":
	//     "$ref": "#/responses/forbidden"
	//   "404":
	//     "$ref": "#/responses/notFound"
	//   "422":
	//     "$ref": "#/responses/validationError"

	form := web.GetForm(ctx).(*api.AddOrgPinnedRepoOption)

	// Get the repository
	repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Organization.ID, form.RepoName)
	if err != nil {
		if repo_model.IsErrRepoNotExist(err) {
			ctx.APIErrorNotFound("GetRepositoryByName", err)
			return
		}
		ctx.APIErrorInternal(err)
		return
	}

	// Create pinned repo
	pinned := &organization.OrgPinnedRepo{
		OrgID:        ctx.Org.Organization.ID,
		RepoID:       repo.ID,
		GroupID:      form.GroupID,
		DisplayOrder: form.DisplayOrder,
	}

	if err := organization.CreateOrgPinnedRepo(ctx, pinned); err != nil {
		if _, ok := err.(organization.ErrOrgPinnedRepoAlreadyExist); ok {
			ctx.APIError(http.StatusUnprocessableEntity, "Repository is already pinned")
			return
		}
		ctx.APIErrorInternal(err)
		return
	}

	// Load the repo details
	pinned.Repo = repo
	if pinned.GroupID > 0 {
		pinned.Group, _ = organization.GetOrgPinnedGroup(ctx, pinned.GroupID)
	}

	ctx.JSON(http.StatusCreated, convertOrgPinnedRepo(ctx, pinned))
}

// DeletePinnedRepo unpins a repository from an organization
func DeletePinnedRepo(ctx *context.APIContext) {
	// swagger:operation DELETE /orgs/{org}/pinned/{repo} organization orgDeletePinnedRepo
	// ---
	// summary: Unpin a repository from an organization
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// - name: repo
	//   in: path
	//   description: name of the repository
	//   type: string
	//   required: true
	// responses:
	//   "204":
	//     "$ref": "#/responses/empty"
	//   "403":
	//     "$ref": "#/responses/forbidden"
	//   "404":
	//     "$ref": "#/responses/notFound"

	repoName := ctx.PathParam("repo")

	// Get the repository
	repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Organization.ID, repoName)
	if err != nil {
		if repo_model.IsErrRepoNotExist(err) {
			ctx.APIErrorNotFound("GetRepositoryByName", err)
			return
		}
		ctx.APIErrorInternal(err)
		return
	}

	if err := organization.DeleteOrgPinnedRepo(ctx, ctx.Org.Organization.ID, repo.ID); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.Status(http.StatusNoContent)
}

// ReorderPinnedRepos updates the order of pinned repositories
func ReorderPinnedRepos(ctx *context.APIContext) {
	// swagger:operation PUT /orgs/{org}/pinned/reorder organization orgReorderPinnedRepos
	// ---
	// summary: Reorder pinned repositories
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// - name: body
	//   in: body
	//   required: true
	//   schema:
	//     "$ref": "#/definitions/ReorderOrgPinnedReposOption"
	// responses:
	//   "204":
	//     "$ref": "#/responses/empty"
	//   "403":
	//     "$ref": "#/responses/forbidden"

	form := web.GetForm(ctx).(*api.ReorderOrgPinnedReposOption)

	// Convert API order to model order
	orders := make([]organization.PinnedRepoOrder, len(form.Orders))
	for i, o := range form.Orders {
		orders[i] = organization.PinnedRepoOrder{
			RepoID:       o.RepoID,
			GroupID:      o.GroupID,
			DisplayOrder: o.DisplayOrder,
		}
	}

	if err := organization.ReorderOrgPinnedRepos(ctx, ctx.Org.Organization.ID, orders); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.Status(http.StatusNoContent)
}

// ListPinnedGroups returns the pinned groups for an organization
func ListPinnedGroups(ctx *context.APIContext) {
	// swagger:operation GET /orgs/{org}/pinned/groups organization orgListPinnedGroups
	// ---
	// summary: List an organization's pinned repository groups
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// responses:
	//   "200":
	//     "$ref": "#/responses/OrgPinnedGroupList"
	//   "404":
	//     "$ref": "#/responses/notFound"

	groups, err := organization.GetOrgPinnedGroups(ctx, ctx.Org.Organization.ID)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	apiGroups := make([]*api.OrgPinnedGroup, len(groups))
	for i, g := range groups {
		apiGroups[i] = convertOrgPinnedGroup(g)
	}

	ctx.JSON(http.StatusOK, apiGroups)
}

// CreatePinnedGroup creates a new pinned group for an organization
func CreatePinnedGroup(ctx *context.APIContext) {
	// swagger:operation POST /orgs/{org}/pinned/groups organization orgCreatePinnedGroup
	// ---
	// summary: Create a pinned repository group
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// - name: body
	//   in: body
	//   required: true
	//   schema:
	//     "$ref": "#/definitions/CreateOrgPinnedGroupOption"
	// responses:
	//   "201":
	//     "$ref": "#/responses/OrgPinnedGroup"
	//   "403":
	//     "$ref": "#/responses/forbidden"

	form := web.GetForm(ctx).(*api.CreateOrgPinnedGroupOption)

	group := &organization.OrgPinnedGroup{
		OrgID:        ctx.Org.Organization.ID,
		Name:         form.Name,
		DisplayOrder: form.DisplayOrder,
		Collapsed:    form.Collapsed,
	}

	if err := organization.CreateOrgPinnedGroup(ctx, group); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusCreated, convertOrgPinnedGroup(group))
}

// UpdatePinnedGroup updates a pinned group
func UpdatePinnedGroup(ctx *context.APIContext) {
	// swagger:operation PUT /orgs/{org}/pinned/groups/{id} organization orgUpdatePinnedGroup
	// ---
	// summary: Update a pinned repository group
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// - name: id
	//   in: path
	//   description: id of the group
	//   type: integer
	//   format: int64
	//   required: true
	// - name: body
	//   in: body
	//   required: true
	//   schema:
	//     "$ref": "#/definitions/UpdateOrgPinnedGroupOption"
	// responses:
	//   "200":
	//     "$ref": "#/responses/OrgPinnedGroup"
	//   "403":
	//     "$ref": "#/responses/forbidden"
	//   "404":
	//     "$ref": "#/responses/notFound"

	form := web.GetForm(ctx).(*api.UpdateOrgPinnedGroupOption)
	groupID := ctx.PathParamInt64("id")

	group, err := organization.GetOrgPinnedGroup(ctx, groupID)
	if err != nil {
		if _, ok := err.(organization.ErrOrgPinnedGroupNotExist); ok {
			ctx.APIErrorNotFound("GetOrgPinnedGroup", err)
			return
		}
		ctx.APIErrorInternal(err)
		return
	}

	// Verify group belongs to this org
	if group.OrgID != ctx.Org.Organization.ID {
		ctx.APIErrorNotFound()
		return
	}

	if form.Name != nil {
		group.Name = *form.Name
	}
	if form.DisplayOrder != nil {
		group.DisplayOrder = *form.DisplayOrder
	}
	if form.Collapsed != nil {
		group.Collapsed = *form.Collapsed
	}

	if err := organization.UpdateOrgPinnedGroup(ctx, group); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusOK, convertOrgPinnedGroup(group))
}

// DeletePinnedGroup deletes a pinned group
func DeletePinnedGroup(ctx *context.APIContext) {
	// swagger:operation DELETE /orgs/{org}/pinned/groups/{id} organization orgDeletePinnedGroup
	// ---
	// summary: Delete a pinned repository group
	// produces:
	// - application/json
	// parameters:
	// - name: org
	//   in: path
	//   description: name of the organization
	//   type: string
	//   required: true
	// - name: id
	//   in: path
	//   description: id of the group
	//   type: integer
	//   format: int64
	//   required: true
	// responses:
	//   "204":
	//     "$ref": "#/responses/empty"
	//   "403":
	//     "$ref": "#/responses/forbidden"
	//   "404":
	//     "$ref": "#/responses/notFound"

	groupID := ctx.PathParamInt64("id")

	group, err := organization.GetOrgPinnedGroup(ctx, groupID)
	if err != nil {
		if _, ok := err.(organization.ErrOrgPinnedGroupNotExist); ok {
			ctx.APIErrorNotFound("GetOrgPinnedGroup", err)
			return
		}
		ctx.APIErrorInternal(err)
		return
	}

	// Verify group belongs to this org
	if group.OrgID != ctx.Org.Organization.ID {
		ctx.APIErrorNotFound()
		return
	}

	if err := organization.DeleteOrgPinnedGroup(ctx, groupID); err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	ctx.Status(http.StatusNoContent)
}

// convertOrgPinnedRepo converts a pinned repo to API format
func convertOrgPinnedRepo(ctx *context.APIContext, p *organization.OrgPinnedRepo) *api.OrgPinnedRepo {
	result := &api.OrgPinnedRepo{
		ID:           p.ID,
		RepoID:       p.RepoID,
		GroupID:      p.GroupID,
		DisplayOrder: p.DisplayOrder,
	}

	if p.Repo != nil {
		if repo, ok := p.Repo.(*repo_model.Repository); ok {
			result.Repo = convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeRead})
		}
	}

	if p.Group != nil {
		result.Group = convertOrgPinnedGroup(p.Group)
	}

	return result
}

// convertOrgPinnedGroup converts a pinned group to API format
func convertOrgPinnedGroup(g *organization.OrgPinnedGroup) *api.OrgPinnedGroup {
	return &api.OrgPinnedGroup{
		ID:           g.ID,
		Name:         g.Name,
		DisplayOrder: g.DisplayOrder,
		Collapsed:    g.Collapsed,
	}
}
