package message

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tidbcloud/devops/api"
	msgapi "github.com/tidbcloud/devops/api/message"
	msgent "github.com/tidbcloud/devops/entity/message"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/log"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/workqueue"
	msgsvcapi "github.com/tidbcloud/devops/pkg/service/message/api"

	"github.com/gin-gonic/gin"
	"go.uber.org/dig"
)

const defaultSendEmailBatchPerBatch = 100

type ControllerParam struct {
	dig.In

	Message msgsvcapi.MessageServiceAPI
}

type MessageController struct {
	Message msgsvcapi.MessageServiceAPI
}

func (c *MessageController) Name() string {
	return "MessageController"
}

func NewMessageController(p ControllerParam) ctrl.Controller {
	return &MessageController{
		Message: p.Message,
	}
}

func (c *MessageController) RegisterAPI(suite *ctrl.Suite) error {
	r := suite.Router.Group("/message")

	emailG := r.Group("/email")
	{
		emailG.POST("/send_batch", c.SendEmailBatch)
		emailG.POST("/send", c.SendEmail)
		emailG.GET("/records", c.ListEmailRecords)
		emailG.GET("/records/:record_id", c.GetEmailRecord)
	}

	return nil
}

// SendEmailBatch
//
//	@Id				MessageControllerSendEmailBatch
//	@Description	send emails batch
//	@Tags			MessageController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		msgapi.SendEmailBatchRequest	true	"request"
//	@Success		200	{object}	msgapi.SendEmailBatchResponse
//	@Failure		500	{object}	api.BaseResp
//	@Router			/api/v1/devops/message/email/send_batch [post]
func (c *MessageController) SendEmailBatch(ctx *gin.Context) {
	req := &msgapi.SendEmailBatchRequest{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("user email not found in ctx"), api.ErrCodeInternal)
		return
	}

	targets := buildSendEmailBatchTargets(req)
	perBatch := req.PerBatch
	if perBatch <= 0 {
		perBatch = defaultSendEmailBatchPerBatch
	}

	successTargets := container.NewSlice[msgent.EmailTarget]()
	failedTargets := container.NewSlice[msgent.EmailTarget]()
	wq := workqueue.NewParallelizer(targets, req.Concurrency, perBatch)
	wq.Do(ctx.Request.Context(), func(ctx context.Context, targets []msgent.EmailTarget) {
		succeeded, failed, err := c.Message.SendBulkEmails(ctx, msgsvcapi.SendBulkEmailsParam{
			Name:        req.Name,
			Targets:     targets,
			Template:    req.Template,
			Reason:      req.Reason,
			Applicant:   email,
			FromAddress: req.FromAddress,
		})
		if err != nil {
			log.FromContext(ctx).Error(err, "failed to send bulk emails", "targets", targets)
			failedTargets.Append(targets...)
			return
		}

		successTargets.Append(succeeded...)
		failedTargets.Append(failed...)
	})

	resp := &msgapi.SendEmailBatchResponse{
		SuccessTargets: successTargets.Dump(),
		FailedTargets:  failedTargets.Dump(),
	}

	ctrl.Success(ctx, resp)
}

func buildSendEmailBatchTargets(req *msgapi.SendEmailBatchRequest) []msgent.EmailTarget {
	targetCount := 0
	if req.TargetTenants != nil {
		targetCount += len(req.TargetTenants.TenantIDs)
	}
	if req.TargetUsers != nil {
		targetCount++
	}
	targets := make([]msgent.EmailTarget, 0, targetCount)

	if req.TargetTenants != nil {
		for _, tenantID := range req.TargetTenants.TenantIDs {
			targets = append(targets, msgent.EmailTarget{
				Tenant: &msgent.EmailTargetTenant{
					TenantID: tenantID,
					Role:     req.TargetTenants.Role,
				},
			})
		}
	}
	if req.TargetUsers != nil {
		targets = append(targets, msgent.EmailTarget{
			Users: &msgent.EmailTargetUsers{Emails: req.TargetUsers.Emails},
		})
	}

	return targets
}

// SendEmail
//
//	@Id				MessageControllerSendEmail
//	@Description	send email
//	@Tags			MessageController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		msgapi.SendEmailRequest	true	"request"
//	@Success		200	{object}	api.BaseResp
//	@Failure		500	{object}	api.BaseResp
//	@Router			/api/v1/devops/message/email/send [post]
func (c *MessageController) SendEmail(ctx *gin.Context) {
	req := &msgapi.SendEmailRequest{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("user email not found in ctx"), api.ErrCodeInternal)
		return
	}

	param := msgsvcapi.SendEmailParam{
		Name:        req.Name,
		Target:      req.Target,
		Template:    req.Template,
		Reason:      req.Reason,
		Applicant:   email,
		FromAddress: req.FromAddress,
	}
	err = c.Message.SendEmail(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// ListEmailRecords
//
//	@Id				MessageControllerListEmailRecords
//	@Description	list email records
//	@Tags			MessageController
//	@Accept			json
//	@Produce		json
//	@Param			req	query		msgapi.ListEmailRecordsRequest	false	"request param"
//	@Success		200	{object}	msgapi.ListEmailRecordsResponse
//	@Router			/api/v1/devops/message/email/records [get]
func (c *MessageController) ListEmailRecords(ctx *gin.Context) {
	req := &msgapi.ListEmailRecordsRequest{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := msgsvcapi.ListEmailRecordsParam{
		ReasonLike:    req.ReasonLike,
		FromAddressIs: req.FromAddressIs,
		Page:          int(req.Page),
		PerPage:       int(req.PerPage),
	}
	records, total, err := c.Message.ListEmailRecords(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &msgapi.ListEmailRecordsResponse{
		Records: records,
		Total:   total,
	}
	ctrl.Success(ctx, resp)
}

// GetEmailRecord
//
//	@Id				MessageControllerGetEmailRecord
//	@Description	get a email record
//	@Tags			MessageController
//	@Accept			json
//	@Produce		json
//	@Param			record_id	path		int	true	"id"
//	@Success		200			{object}	msgapi.GetEmailRecordResponse
//	@Router			/api/v1/devops/message/email/records/{record_id} [get]
func (c *MessageController) GetEmailRecord(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("record_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	record, err := c.Message.GetEmailRecord(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &msgapi.GetEmailRecordResponse{
		Record: record,
	}
	ctrl.Success(ctx, resp)
}
