package release

import (
	"errors"
	"fmt"

	"github.com/tidbcloud/devops/api"
	releaseapi "github.com/tidbcloud/devops/api/release"
	gitent "github.com/tidbcloud/devops/entity/github"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	relsvcapi "github.com/tidbcloud/devops/pkg/service/release/api"

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

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

	param := relsvcapi.ListTicketsParam{
		Page:    int(req.Page),
		PerPage: int(req.PerPage),
	}
	if len(req.Names) > 0 {
		param.Names = req.Names
	}
	if len(req.Statuses) > 0 {
		param.Statuses = req.Statuses
	}
	if len(req.Releases) > 0 {
		param.Releases = req.Releases
	}

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

	resp := &releaseapi.ListTicketsResp{Total: total, Tickets: tickets}
	ctrl.Success(ctx, resp)
}

// GetTicket
//
//	@Id				ReleaseControllerGetTicket
//	@Description	get a ticket
//	@Tags			ReleaseController
//	@Accept			json
//	@Produce		json
//	@Param			ticket_name	path		string					true	"ticket name"
//	@Param			req			query		releaseapi.GetTicketReq	false	"request param"
//	@Success		200			{object}	releaseapi.GetTicketResp
//	@Router			/api/v1/devops/release/tickets/{ticket_name} [get]
func (c *ReleaseController) GetTicket(ctx *gin.Context) {
	name := ctx.Param("ticket_name")
	if name == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("ticket name is required"), api.ErrCodeInvalidArgument)
		return
	}

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

	param := relsvcapi.GetTicketParam{
		WithGithubPR: req.WithGithubPR,
	}
	ticket, err := c.relSvc.GetTicket(ctx, name, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &releaseapi.GetTicketResp{Ticket: ticket}
	ctrl.Success(ctx, resp)
}

// UpdateTicket
//
//	@Id				ReleaseControllerUpdateTicket
//	@Description	update a ticket
//	@Tags			ReleaseController
//	@Accept			json
//	@Produce		json
//	@Param			ticket_name	path		string						true	"ticket name"
//	@Param			req			body		releaseapi.UpdateTicketReq	true	"request body"
//	@Success		200			{object}	api.BaseResp
//	@Router			/api/v1/devops/release/tickets/{ticket_name} [put]
func (c *ReleaseController) UpdateTicket(ctx *gin.Context) {
	name := ctx.Param("ticket_name")
	if name == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("ticket name is required"), api.ErrCodeInvalidArgument)
		return
	}

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

	param, err := buildParamForUpdateTicket(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	err = c.relSvc.UpdateTicket(ctx, name, *param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// SyncTicketAsync
//
//	@Id				ReleaseControllerSyncTicketAsync
//	@Description	sync a ticket
//	@Tags			ReleaseController
//	@Accept			json
//	@Produce		json
//	@Param			ticket_name	path		string	true	"ticket name"
//	@Success		200			{object}	api.BaseResp
//	@Router			/api/v1/devops/release/tickets/{ticket_name}/sync_async [post]
func (c *ReleaseController) SyncTicketAsync(ctx *gin.Context) {
	name := ctx.Param("ticket_name")
	if name == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("ticket name is required"), api.ErrCodeInvalidArgument)
		return
	}

	err := c.relSvc.SyncTicketsAsync(ctx, []string{name})
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

func buildParamForUpdateTicket(req *releaseapi.UpdateTicketReq) (*relsvcapi.UpdateTicketParam, error) {
	update := &relsvcapi.UpdateTicketParam{
		GithubPRLocations: nil,
	}
	if req.GithubPRLocations != nil {
		update.GithubPRLocations = pointer.Pointer(
			container.TransformSlice(*req.GithubPRLocations, container.FromString[gitent.PRLocation]),
		)
	}

	// validate
	errs := []error{}
	if update.GithubPRLocations != nil {
		for _, loc := range *update.GithubPRLocations {
			err := loc.Validate()
			if err != nil {
				errs = append(errs, err)
			}
		}
	}
	if len(errs) > 0 {
		return nil, errors.Join(errs...)
	}

	return update, nil
}
