package maintenancewindow

import (
	"context"

	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
)

func (svc *service) UpdateMaintenanceTask(
	ctx context.Context,
	req *tidbmgmtservicev1.UpdateMaintenanceTaskRequest,
) (*tidbmgmtservicev1.MaintenanceTask, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.UpdateMaintenanceTaskSvc")
	defer span.End()

	reqTask := req.GetMaintenanceTask()
	if reqTask == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "missing maintenance task")
	}
	if reqTask.GetMaintenanceTaskId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "missing maintenance task ID")
	}
	updated := &tidbmgmtservicev1.MaintenanceTask{}
	err := svc.transaction.Transaction(ctx, func(ctx context.Context) error {
		task, err := svc.mtRepo.Load(ctx, reqTask.GetMaintenanceTaskId())
		if err != nil {
			return err
		}
		var ev *clusterevent.MaintenanceWindowEvent
		if reqTask.GetScheduledApplyTime() != nil {
			task.ScheduledApplyTime = reqTask.GetScheduledApplyTime()
			if err := svc.checkScheduledApplyTime(&task.MaintenanceTask); err != nil {
				return err
			}
			ev = newMaintenanceTaskEvent(ctx, clusterevent.MaintenanceWindowEventTypeTaskUpdated, task)
		}
		if reqTask.GetState() != tidbmgmtservicev1.MaintenanceTask_STATE_UNSPECIFIED &&
			reqTask.GetState() != task.State {
			switch reqTask.GetState() {
			case tidbmgmtservicev1.MaintenanceTask_PENDING:
				return bizerr.New(bizerr.ErrCodeParams, "cannot set task state to PENDING")
			case tidbmgmtservicev1.MaintenanceTask_RUNNING:
				// Currently, tasks will automatically enter the running status based on the maintenance window time setting.
				return bizerr.New(bizerr.ErrCodeParams, "cannot set task state to RUNNING")
			case tidbmgmtservicev1.MaintenanceTask_DONE:
				task.State = reqTask.GetState()
				ev = newMaintenanceTaskEvent(ctx, clusterevent.MaintenanceWindowEventTypeTaskDone, task)
				filter := &tidbmgmtservicev1.QueryMaintenanceTaskExecutionsRequest_Filter{
					MaintenanceTaskIds: []uint64{task.MaintenanceTaskId},
					States: []tidbmgmtservicev1.MaintenanceTaskExecution_State{
						tidbmgmtservicev1.MaintenanceTaskExecution_PENDING,
						tidbmgmtservicev1.MaintenanceTaskExecution_RUNNING,
					},
				}
				executions, err := svc.mteRepo.Query(ctx, filter, nil)
				if err != nil && err.RawErrCode() != bizerr.ErrCodeRecordNotFound {
					return err
				}
				if len(executions) > 0 {
					return bizerr.New(
						bizerr.ErrCodeParams,
						"cannot update status to done when there are pending or running task executions",
					)
				}
			default:
				return bizerr.New(bizerr.ErrCodeParams, "invalid task state")
			}
		}
		if err := svc.mtRepo.Save(ctx, task); err != nil {
			return err
		}
		updated = &task.MaintenanceTask
		return svc.eventCommitter.Write(ctx, clusterevent.TopicMaintenanceWindowEvent, ev)
	})
	if err != nil {
		return nil, bizerr.WrapError(err, "update maintenance task")
	}

	return updated, nil
}
