package release

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/devops/api"
	releaseapi "github.com/tidbcloud/devops/api/release"
	relent "github.com/tidbcloud/devops/entity/release"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	relsvcapi "github.com/tidbcloud/devops/pkg/service/release/api"

	"github.com/gin-gonic/gin"
)

// ListReleases
//
//	@Id				ReleaseControllerListReleases
//	@Description	list releases
//	@Tags			ReleaseController
//	@Accept			json
//	@Produce		json
//	@Param			req	query		releaseapi.ListReleaseReq	false	"request param"
//	@Success		200	{object}	releaseapi.ListReleaseResp
//	@Router			/api/v1/devops/release/releases [get]
func (c *ReleaseController) ListReleases(ctx *gin.Context) {
	req := &releaseapi.ListReleaseReq{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := relsvcapi.ListReleaseParam{
		GetReleaseParam: relsvcapi.GetReleaseParam{
			WithTickets: false,
		},
		Page:    int(req.Page),
		PerPage: int(req.PerPage),
	}
	if req.Name != "" {
		param.Names = []string{strings.ToLower(req.Name)}
	}
	if req.ReleaseBefore != 0 {
		param.ReleaseBefore = time.Unix(req.ReleaseBefore, 0)
	}
	if req.ReleaseAfter != 0 {
		param.ReleaseAfter = time.Unix(req.ReleaseAfter, 0)
	}

	releases, total, err := c.relSvc.ListReleases(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &releaseapi.ListReleaseResp{Total: total, Releases: releases}
	ctrl.Success(ctx, resp)
}

// GetRelease
//
//	@Id				ReleaseControllerGetRelease
//	@Description	get a release
//	@Tags			ReleaseController
//	@Accept			json
//	@Produce		json
//	@Param			release_id	path		int							true	"release id"
//	@Param			req			query		releaseapi.GetReleaseReq	false	"request param"
//	@Success		200			{object}	releaseapi.GetReleaseResp
//	@Router			/api/v1/devops/release/releases/{release_id} [get]
func (c *ReleaseController) GetRelease(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("release_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	req := &releaseapi.GetReleaseReq{}
	err = ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := relsvcapi.GetReleaseParam{
		WithTickets: req.WithTickets,
	}
	release, err := c.relSvc.GetRelease(ctx, id, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &releaseapi.GetReleaseResp{Release: release}
	ctrl.Success(ctx, resp)
}

// SyncReleaseManually
//
//	@Id				ReleaseControllerSyncReleaseManually
//	@Description	sync all releases manually
//	@Tags			ReleaseController
//	@Accept			json
//	@Produce		json
//	@Success		200	{object}	api.BaseResp
//	@Router			/api/v1/devops-internal/release/releases/sync [post]
func (c *ReleaseController) SyncReleaseManually(ctx *gin.Context) {
	err := c.relSvc.SyncRelease(ctx)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// ListChangesOfRelease
//
//	@Id				ReleaseControllerListChangesOfRelease
//	@Description	list all change of a release
//	@Tags			ReleaseController
//	@Accept			json
//	@Produce		json
//	@Param			release_id	path		int	true	"release id"
//	@Success		200			{object}	releaseapi.ListChangeResp
//	@Router			/api/v1/devops/release/releases/{release_id}/changes [get]
func (c *ReleaseController) ListChangesOfRelease(ctx *gin.Context) {
	id, err := strconv.Atoi(ctx.Param("release_id"))
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	param := relsvcapi.ListChangeParam{
		Releases: []uint64{uint64(id)},
	}
	changes, err := c.chgSvc.ListChanges(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &releaseapi.ListChangeResp{
		Changes: changes,
	}

	// order changes to make changes page in web more readable
	sort.SliceStable(resp.Changes, func(i, j int) bool {
		c1 := resp.Changes[i]
		c2 := resp.Changes[j]

		// closed changes are always at the end
		if c1.Status == relent.ChangeStatusClosed && c2.Status == relent.ChangeStatusClosed {
			return c1.CreatedAt.After(c2.CreatedAt)
		}
		if c1.Status == relent.ChangeStatusClosed {
			return false
		}
		if c2.Status == relent.ChangeStatusClosed {
			return true
		}

		// sort by created time
		return c1.CreatedAt.After(c2.CreatedAt)
	})

	ctrl.Success(ctx, resp)
}
