package remediationticket

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"github.com/tidbcloud/devops/entity"
	apprent "github.com/tidbcloud/devops/entity/approval"
	"github.com/tidbcloud/devops/pkg/infra/log"
	apprapi "github.com/tidbcloud/devops/pkg/service/approval/api"
	"github.com/tidbcloud/devops/pkg/service/remediationticket/actors"
	rmhelpers "github.com/tidbcloud/devops/pkg/service/remediationticket/helpers"
	rtphase "github.com/tidbcloud/devops/pkg/service/remediationticket/phase"

	rtsvcapi "github.com/tidbcloud/devops/pkg/service/remediationticket/api"
	rtRepo "github.com/tidbcloud/devops/pkg/service/remediationticket/repo"
)

func (s *svc) ListTickets(ctx context.Context, param rtsvcapi.ListTicketParam) ([]*entity.RemediationTicket, int64, error) {
	arr, err := s.repo.QueryTicket(ctx)
	if err != nil {
		return nil, 0, err
	}

	return arr, int64(len(arr)), nil
}

func (s *svc) GetTicket(ctx context.Context, id uint64) (*entity.RemediationTicket, error) {
	return s.repo.LoadTicket(ctx, id)
}

func (s *svc) CreateTicket(ctx context.Context, param rtsvcapi.CreateTicketParam) (*uint64, error) {
	_, err := s.incidentSvc.GetIncident(ctx, param.IncidentID)
	if err != nil {
		return nil, fmt.Errorf("create remediation ticket failed when get incident %d: %s", param.IncidentID, err)
	}

	if param.ClusterID == nil {
		return nil, fmt.Errorf("create remediation ticket failed: cluster_id cannot be nil")
	}

	// Load SLA Info
	var downtimeCount int
	var slaVal float64
	clusterIDString := strconv.FormatUint(*param.ClusterID, 10)
	slaDetail, err := s.dashboardSvc.GetClusterDetailOfDedicatedSLA(ctx, param.Month, clusterIDString, true)
	if err != nil {
		// return nil, fmt.Errorf("get sla failed for cluster_id %d failed: %w", *param.ClusterID, err)
		// HACK: make a workaround for serverless clusters
		s.logger.Info("get sla failed", "cluster_id", *param.ClusterID, "errror", err)
		downtimeCount = 0
		slaVal = 0.0
	} else {
		downtimeCount = len(slaDetail.DowntimeList)
		slaVal = slaDetail.SLAValue
	}

	var ticketId uint64
	txnErr := s.dbp.Transaction(ctx, func(ctx context.Context) error {
		// Begin with pending status
		initialPhase := entity.RemediationTicketPhaseCreated
		ent := &entity.RemediationTicket{
			AssociatedIncidentId:    param.IncidentID,
			Phase:                   &initialPhase,
			Status:                  entity.RemediationTicketStatusPending,
			BizRegion:               param.BizRegion,
			ApplicantEmail:          param.ApplicantEmail,
			CurrentResponsible:      param.ApplicantEmail,
			TenantID:                param.TenantID,
			ClusterID:               param.ClusterID,
			Customer:                &param.Customer,
			Month:                   param.Month,
			ClusterType:             param.ClusterType,
			MonthlyUptimePercentage: fmt.Sprintf("%.2f", slaVal),
			AccumulatedDownMinutes:  &downtimeCount,
		}

		ticketId, err := s.repo.SaveTicket(ctx, ent)
		if err != nil {
			return fmt.Errorf("create remediation ticket failed: %w", err)
		}

		// Transfer to approving
		ticket, err := s.repo.LoadTicket(ctx, ticketId)
		if err != nil {
			return err
		}

		ctx = log.WrapContext(ctx, "ticket_id", ticket.ID, "ticket_phase", ticket.Phase, "ticket_status", ticket.Status)
		actor := actors.NewRemediationTicketActor(ctx, s.insComps(ctx), ticket)

		err = actor.SendApproval(rtsvcapi.TransferTicketParam{})
		if err != nil {
			return err
		}

		return nil
	})

	return &ticketId, txnErr
}

// DEPRECATED
func (s *svc) CreateTicketAndTransferToRegionTSELeader(ctx context.Context, param rtsvcapi.CreateTicketParam) (*uint64, error) {
	initialPhase := entity.RemediationTicketPhaseRegionTSELeader
	createPhase := entity.RemediationTicketPhaseCreated
	ent := &entity.RemediationTicket{
		AssociatedIncidentId: param.IncidentID,
		Phase:                &initialPhase,
	}
	id, err := s.repo.SaveTicket(ctx, ent)
	if err != nil {
		return nil, err
	}
	time := time.Now()
	err = s.repo.CreateTicketLog(ctx, &entity.RemediationTicketLog{TicketId: id, CreatedAt: &time, ToPhase: &createPhase})
	if err != nil {
		return nil, err
	}
	err = s.repo.CreateTicketLog(ctx, &entity.RemediationTicketLog{TicketId: id, CreatedAt: &time, ToPhase: &initialPhase})
	if err != nil {
		return nil, err
	}
	return &id, nil
}

func (s *svc) CancelTicket(ctx context.Context, id uint64, param rtsvcapi.CancelTicketParam) error {
	txnErr := s.dbp.Transaction(ctx, func(ctx context.Context) error {
		ticket, err := s.repo.LoadTicket(ctx, id)
		if err != nil {
			return err
		}

		// Canceller check
		if ticket.ApplicantEmail != param.CancellerEmail {
			return fmt.Errorf("submitter check failed, received canceller: %s, ticket applicant: %s", param.CancellerEmail, ticket.CurrentResponsible)
		}

		ctx = log.WrapContext(ctx, "ticket_id", ticket.ID, "ticket_phase", ticket.Phase, "ticket_status", ticket.Status)
		actor := actors.NewRemediationTicketActor(ctx, s.insComps(ctx), ticket)

		err = actor.Cancel()
		if err != nil {
			return err
		}

		return nil
	})

	return txnErr
}

func (s *svc) SubmitPhaseTransitionApproval(ctx context.Context, id uint64, param rtsvcapi.TransferTicketParam) error {
	txnErr := s.dbp.Transaction(ctx, func(ctx context.Context) error {
		// s.repo.FunctionalPatchTicket(ctx, )
		ticket, err := s.repo.LoadTicket(ctx, id)
		if err != nil {
			return err
		}

		// Version check
		if ticket.Version != param.Version {
			return fmt.Errorf("version check failed, received version: %d, current ticket version: %d", param.Version, ticket.Version)
		}

		// Reponsible person check
		if ticket.CurrentResponsible != param.SubmitterEmail {
			return fmt.Errorf("submitter check failed, received submitter: %s, current responsible person: %s", param.SubmitterEmail, ticket.CurrentResponsible)
		}

		ctx = log.WrapContext(ctx, "ticket_id", ticket.ID, "ticket_phase", ticket.Phase, "ticket_status", ticket.Status)
		actor := actors.NewRemediationTicketActor(ctx, s.insComps(ctx), ticket)

		switch ticket.Status {
		case entity.RemediationTicketStatusPending:
			patch := param
			err = actor.SendApproval(patch)
		case entity.RemediationTicketStatusRejected:
			patch := param
			err = actor.SendApproval(patch)
		}

		return err
	})

	return txnErr
}

// TODO: add version check
func (s *svc) ApproveTicketPhase(ctx context.Context, id uint64, param rtsvcapi.ApprovePhaseParam) error {
	if param.ApprID == 0 {
		return fmt.Errorf("apprPhaseParam.ApprID cannot be 0")
	}
	if err := s.validateApprovePhaseInput(ctx, id, param); err != nil {
		return err
	}
	err := s.apprSvc.FinishApprovalTask(ctx, param.ApprID, apprapi.FinishApprovalTaskParam{
		User:   param.ApproverEmail,
		Result: apprent.ApprovalTaskStatusApproved,
	})
	if err != nil {
		return fmt.Errorf("approve ticket %d failed: %w", id, err)
	}

	err = s.SyncTickets(ctx, id)
	if err != nil {
		return fmt.Errorf("sync ticket %d when approving ticket phase failed: %w", id, err)
	}

	err = s.submitApprovedPhaseInput(ctx, id, param)
	if err != nil {
		return fmt.Errorf("submit approved phase input for ticket %d failed: %w", id, err)
	}

	err = s.advanceNoInputPhases(ctx, id)
	if err != nil {
		return fmt.Errorf("advance no-input phases for ticket %d failed: %w", id, err)
	}

	return nil
}

func (s *svc) validateApprovePhaseInput(ctx context.Context, id uint64, param rtsvcapi.ApprovePhaseParam) error {
	ticketApproval, err := s.GetTicketApprovalByApprID(ctx, param.ApprID)
	if err != nil {
		return err
	}
	if ticketApproval.TicketId != id {
		return fmt.Errorf("ticket approval record does not match requested ticket id")
	}

	meta := rtphase.Metadata(ticketApproval.ToPhase)
	if !meta.RequiresInput {
		return nil
	}

	ticket, err := s.repo.LoadTicket(ctx, id)
	if err != nil {
		return fmt.Errorf("load ticket failed: %w", err)
	}
	if ticket == nil {
		return fmt.Errorf("ticket %d not found", id)
	}

	ticketForValidation := *ticket
	ticketForValidation.Phase = &ticketApproval.ToPhase
	targetPhase, err := rtphase.GetPhaseByName(ticketApproval.ToPhase)
	if err != nil {
		return fmt.Errorf("get approved target phase %s failed: %w", ticketApproval.ToPhase, err)
	}
	if err := targetPhase.ValidateAndUpdateTicket(&ticketForValidation, param.Input); err != nil {
		return fmt.Errorf("input is required for phase %s: %w", ticketApproval.ToPhase, err)
	}

	return nil
}

func (s *svc) submitApprovedPhaseInput(ctx context.Context, id uint64, param rtsvcapi.ApprovePhaseParam) error {
	ticket, err := s.repo.LoadTicket(ctx, id)
	if err != nil {
		return fmt.Errorf("load ticket failed: %w", err)
	}
	if ticket == nil {
		return fmt.Errorf("ticket %d not found", id)
	}
	if ticket.Phase == nil {
		return fmt.Errorf("ticket %d phase is nil", id)
	}
	if ticket.Status != entity.RemediationTicketStatusPending {
		return nil
	}

	meta := rtphase.Metadata(*ticket.Phase)
	if !meta.RequiresInput {
		return nil
	}

	actor := actors.NewRemediationTicketActor(ctx, s.insComps(ctx), ticket)
	input := param.Input
	input.Version = ticket.Version
	input.SubmitterEmail = ticket.CurrentResponsible
	if err := actor.SendApproval(input); err != nil {
		return fmt.Errorf("submit input for phase %s failed: %w", *ticket.Phase, err)
	}

	return nil
}

func (s *svc) advanceNoInputPhases(ctx context.Context, id uint64) error {
	maxAdvance := rtphase.WorkflowLength()
	if maxAdvance <= 0 {
		maxAdvance = len(entity.RemediationTicketValidPhases) + 1
	}

	for i := 0; i < maxAdvance; i++ {
		ticket, err := s.repo.LoadTicket(ctx, id)
		if err != nil {
			return fmt.Errorf("load ticket failed: %w", err)
		}
		if ticket == nil {
			return fmt.Errorf("ticket %d not found", id)
		}
		if ticket.Phase == nil {
			return fmt.Errorf("ticket %d phase is nil", id)
		}
		if ticket.Status != entity.RemediationTicketStatusPending {
			return nil
		}

		meta := rtphase.Metadata(*ticket.Phase)
		if meta.RequiresInput || !meta.AutoAdvance || meta.IsFinalized {
			return nil
		}

		actor := actors.NewRemediationTicketActor(ctx, s.insComps(ctx), ticket)
		err = actor.SendApproval(rtsvcapi.TransferTicketParam{
			Version:        ticket.Version,
			SubmitterEmail: ticket.CurrentResponsible,
		})
		if err != nil {
			return fmt.Errorf("auto advance phase %s failed: %w", *ticket.Phase, err)
		}
	}

	return fmt.Errorf("auto advance exceeded workflow length for ticket %d", id)
}

// TODO: add version check
func (s *svc) RejectTicketPhase(ctx context.Context, id uint64, param rtsvcapi.RejectPhaseParam) error {
	if param.ApprID == 0 {
		return fmt.Errorf("apprPhaseParam.ApprID cannot be 0")
	}

	if len(param.Message) != 0 {
		txnErr := s.dbp.Transaction(ctx, func(ctx context.Context) error {
			opt := rtRepo.RemediationTicketPhaseApprovalOpts.ApprovalIDIs(param.ApprID)
			ticketApprovals, err := s.repo.QueryTicketPhaseApproval(ctx, opt)
			if err != nil {
				return fmt.Errorf("get ticket approval for appr id %d failed: %w", param.ApprID, err)
			}
			if len(ticketApprovals) == 0 {
				return fmt.Errorf("cannot find ticket approval for appr id %d", param.ApprID)
			}

			// redundant validation, shouldn't be here
			if ticketApprovals[0].TicketId != id {
				return fmt.Errorf("ticket approval record does not match requested ticket id")
			}

			// update message for ticket approval
			updateMessageFunc := func(current *entity.RemediationTicketPhaseApproval) error {
				current.Message = param.Message
				return nil
			}
			err = s.repo.FunctionalPatchTicketPhaseApproval(ctx, ticketApprovals[0], updateMessageFunc)
			if err != nil {
				return fmt.Errorf("save message to ticket approval failed: %w", err)
			}

			return nil
		})
		if txnErr != nil {
			return fmt.Errorf("cannot update reject approval message: %w", txnErr)
		}
	}

	err := s.apprSvc.FinishApprovalTask(ctx, param.ApprID, apprapi.FinishApprovalTaskParam{
		User:   param.RejecterEmail,
		Result: apprent.ApprovalTaskStatusRejected,
	})
	if err != nil {
		return fmt.Errorf("approve ticket %d failed: %w", id, err)
	}

	err = s.SyncTickets(ctx, id)
	if err != nil {
		return fmt.Errorf("sync ticket %d when rejecting ticket phase failed: %w", id, err)
	}

	return nil

}

// DEPRECATED: Transfer phase
func (s *svc) TransferTicketPhase(ctx context.Context, id uint64, param rtsvcapi.TransferTicketParam) error {
	patch := &entity.RemediationTicket{
		Reason:     param.Reason,
		ReportLink: param.ReportLink,
	}
	err := s.repo.PatchTicket(ctx, patch)
	if err != nil {
		return err
	}
	time := time.Now()
	return s.repo.CreateTicketLog(ctx, &entity.RemediationTicketLog{TicketId: id, CreatedAt: &time})
}

// DEPRECATED
func (s *svc) GetTicketProgress(ctx context.Context, id uint64) ([]entity.RemediationTicketPhaseInfo, entity.RemediationTicketPhase, error) {
	arr := make([]entity.RemediationTicketPhaseInfo, 0, len(entity.RemediationTicketValidPhases))
	defaultPhase := entity.RemediationTicketPhaseCreated
	ticket, err := s.repo.LoadTicket(ctx, id)
	if err != nil {
		return arr, defaultPhase, err
	}

	logArr, err := s.repo.QueryTicketLog(ctx, ticket.ID)
	if err != nil {
		return arr, defaultPhase, err
	}

	phaseMap := make(map[entity.RemediationTicketPhase]*entity.RemediationTicketLog)
	for _, logItem := range logArr {
		if logItem.ToPhase == nil {
			continue
		}
		oldLog, ok := phaseMap[*logItem.ToPhase]
		if ok {
			if logItem.CreatedAt != nil && (oldLog.CreatedAt == nil || logItem.CreatedAt.After(*oldLog.CreatedAt)) {
				phaseMap[*logItem.ToPhase] = logItem
			}
		} else {
			phaseMap[*logItem.ToPhase] = logItem
		}
	}

	for _, phase := range entity.RemediationTicketValidPhases {
		val, ok := phaseMap[phase]
		if ok {
			phaseInfo := entity.RemediationTicketPhaseInfo{
				Phase:  phase,
				Status: entity.RemediationTicketPhaseStatusApproved,
				Time:   val.CreatedAt,
			}
			arr = append(arr, phaseInfo)
		} else {
			phaseInfo := entity.RemediationTicketPhaseInfo{
				Phase:  phase,
				Status: entity.RemediationTicketPhaseStatusUnreached,
			}
			arr = append(arr, phaseInfo)
		}
	}

	return arr, *ticket.Phase, nil
}

func (s *svc) GetTicketApprovalByApprID(ctx context.Context, apprId uint64) (*entity.RemediationTicketPhaseApproval, error) {
	opt := rtRepo.RemediationTicketPhaseApprovalOpts.ApprovalIDIs(apprId)
	ticketApprovals, err := s.repo.QueryTicketPhaseApproval(ctx, opt)
	if err != nil {
		return nil, fmt.Errorf("get ticket Approval failed: %w", err)
	} else if len(ticketApprovals) == 0 {
		return nil, fmt.Errorf("ticket approval not found for apprID %d", apprId)
	}

	return ticketApprovals[0], nil
}

func (s *svc) GetTicketApprovals(ctx context.Context, ticketId uint64) ([]*entity.RemediationTicketPhaseApproval, error) {
	opt := rtRepo.RemediationTicketPhaseApprovalOpts.TicketIDIs(ticketId)
	ticketApprovals, err := s.repo.QueryTicketPhaseApproval(ctx, opt)
	if err != nil {
		return nil, fmt.Errorf("get ticket Approval failed: %w", err)
	}

	return ticketApprovals, nil
}

func (s *svc) GetApprovalFlow(ctx context.Context, ticketId uint64) (*entity.RemediationApprovalFlow, error) {
	ticket, err := s.repo.LoadTicket(ctx, ticketId)
	if err != nil {
		return nil, fmt.Errorf("get ticket failed: %w", err)
	}

	approvalByPhase := make(map[entity.RemediationTicketPhase]*entity.RemediationTicketPhaseApproval)
	ticketApprovals, err := s.GetTicketApprovals(ctx, ticketId)
	if err != nil {
		return nil, err
	}
	for _, ta := range ticketApprovals {
		if ta == nil {
			continue
		}
		approvalByPhase[ta.ToPhase] = ta
	}

	currentDisplayPhase := remediationApprovalFlowCurrentPhase(ticket, approvalByPhase)
	nodes := make([]*entity.RemediationApprovalFlowNode, 0, len(remediationApprovalFlowPhases()))

	for _, phase := range remediationApprovalFlowPhases() {
		meta := rtphase.Metadata(phase)
		node := &entity.RemediationApprovalFlowNode{
			Phase:       phase,
			Label:       meta.Label,
			Conditional: meta.IsOptional,
			Status:      entity.RemediationApprovalFlowNodeStatusUnreached,
			Approvers:   make([]*entity.RemediationApprovalFlowApprover, 0),

			RequiresInput: meta.RequiresInput,
			AutoAdvance:   meta.AutoAdvance,
			Action:        entity.RemediationApprovalFlowNodeActionNone,
		}

		if remediationApprovalFlowIsSkipped(ticket, phase, approvalByPhase) {
			node.Status = entity.RemediationApprovalFlowNodeStatusSkipped
			nodes = append(nodes, node)
			continue
		}

		ta := approvalByPhase[phase]
		if ta != nil {
			node.ApprovalID = &ta.ApprovalID
			if ta.CreatedAt != nil {
				node.OperatedAt = ta.CreatedAt
			}
			if ta.Message != "" {
				msg := ta.Message
				node.Message = &msg
			}
		}

		switch {
		case phase == currentDisplayPhase:
			node.Current = true
			node.Status = remediationApprovalFlowCurrentNodeStatus(ticket)
			node.Action = remediationApprovalFlowCurrentNodeAction(ticket, meta)
		case remediationApprovalFlowReached(ticket, phase, approvalByPhase):
			node.Status = entity.RemediationApprovalFlowNodeStatusApproved
		default:
			node.Status = entity.RemediationApprovalFlowNodeStatusUnreached
		}

		approvers, operator, operatedAt, msg := s.remediationApprovalFlowApprovers(ctx, ticket, phase, ta, ta != nil)
		node.Approvers = approvers
		if operator != nil {
			node.Operator = operator
		}
		if operatedAt != nil {
			node.OperatedAt = operatedAt
		}
		if msg != nil {
			node.Message = msg
		}

		nodes = append(nodes, node)
	}

	return &entity.RemediationApprovalFlow{
		TicketID:      ticket.ID,
		CurrentPhase:  currentDisplayPhase,
		CurrentStatus: ticket.Status,
		Nodes:         nodes,
	}, nil
}

func remediationApprovalFlowPhases() []entity.RemediationTicketPhase {
	return rtphase.OrderedPhases()
}

func remediationApprovalFlowCurrentPhase(ticket *entity.RemediationTicket, approvalByPhase map[entity.RemediationTicketPhase]*entity.RemediationTicketPhaseApproval) entity.RemediationTicketPhase {
	if ticket == nil || ticket.Phase == nil {
		return entity.RemediationTicketPhaseCreated
	}
	if ticket.Status == entity.RemediationTicketStatusApproving && ticket.ApprID != 0 {
		for phase, ta := range approvalByPhase {
			if ta != nil && ta.ApprovalID == ticket.ApprID {
				return phase
			}
		}
	}
	return *ticket.Phase
}

func remediationApprovalFlowCurrentNodeStatus(ticket *entity.RemediationTicket) entity.RemediationApprovalFlowNodeStatus {
	switch ticket.Status {
	case entity.RemediationTicketStatusPending:
		return entity.RemediationApprovalFlowNodeStatusPending
	case entity.RemediationTicketStatusApproving:
		return entity.RemediationApprovalFlowNodeStatusApproving
	case entity.RemediationTicketStatusRejected:
		return entity.RemediationApprovalFlowNodeStatusRejected
	case entity.RemediationTicketStatusClosed:
		return entity.RemediationApprovalFlowNodeStatusApproved
	default:
		return entity.RemediationApprovalFlowNodeStatusApproved
	}
}

func remediationApprovalFlowCurrentNodeAction(ticket *entity.RemediationTicket, meta rtphase.PhaseMetadata) entity.RemediationApprovalFlowNodeAction {
	if ticket == nil || ticket.Phase == nil {
		return entity.RemediationApprovalFlowNodeActionNone
	}

	switch ticket.Status {
	case entity.RemediationTicketStatusApproving:
		if meta.RequiresInput {
			return entity.RemediationApprovalFlowNodeActionApproveWithInput
		}
		return entity.RemediationApprovalFlowNodeActionApprove
	case entity.RemediationTicketStatusPending, entity.RemediationTicketStatusRejected:
		if meta.RequiresInput {
			return entity.RemediationApprovalFlowNodeActionSubmitInput
		}
		return entity.RemediationApprovalFlowNodeActionNone
	default:
		return entity.RemediationApprovalFlowNodeActionNone
	}
}

func remediationApprovalFlowReached(ticket *entity.RemediationTicket, phase entity.RemediationTicketPhase, approvalByPhase map[entity.RemediationTicketPhase]*entity.RemediationTicketPhaseApproval) bool {
	if ticket == nil {
		return false
	}
	if phase == entity.RemediationTicketPhaseCreated {
		return true
	}
	if ticket.Phase != nil && *ticket.Phase == phase && ticket.Status != entity.RemediationTicketStatusApproving {
		return false
	}
	_, ok := approvalByPhase[phase]
	return ok
}

func remediationApprovalFlowIsSkipped(ticket *entity.RemediationTicket, phase entity.RemediationTicketPhase, approvalByPhase map[entity.RemediationTicketPhase]*entity.RemediationTicketPhaseApproval) bool {
	if ticket == nil {
		return false
	}
	if phase != entity.RemediationTicketPhaseCommercialOperation {
		return false
	}
	if _, ok := approvalByPhase[entity.RemediationTicketPhaseCommercialOperation]; ok {
		return false
	}
	if ticket.Phase == nil {
		return false
	}
	switch *ticket.Phase {
	case entity.RemediationTicketPhaseRegionalGM, entity.RemediationTicketPhaseSucceed, entity.RemediationTicketPhaseClosed:
		return true
	default:
		return false
	}
}

func (s *svc) remediationApprovalFlowApprovers(ctx context.Context, ticket *entity.RemediationTicket, phase entity.RemediationTicketPhase, ta *entity.RemediationTicketPhaseApproval, preferApprovalData bool) ([]*entity.RemediationApprovalFlowApprover, *string, *time.Time, *string) {
	if phase == entity.RemediationTicketPhaseCreated {
		var operator *string
		var operatedAt *time.Time
		if ticket != nil {
			if ticket.ApplicantEmail != "" {
				op := ticket.ApplicantEmail
				operator = &op
			}
			if ticket.CreatedAt != 0 {
				tm := time.Unix(ticket.CreatedAt, 0)
				operatedAt = &tm
			}
		}
		return make([]*entity.RemediationApprovalFlowApprover, 0), operator, operatedAt, nil
	}

	if preferApprovalData && ta != nil {
		appr, err := s.apprSvc.GetApproval(ctx, ta.ApprovalID)
		if err == nil && appr != nil {
			approvers, operator, operatedAt := remediationApprovalFlowApproversFromApproval(appr)
			var msg *string
			if ta.Message != "" {
				m := ta.Message
				msg = &m
			}
			return approvers, operator, operatedAt, msg
		}
	}

	approvers := s.remediationApprovalFlowDefaultApprovers(ticket, phase)
	var msg *string
	if ta != nil && ta.Message != "" {
		m := ta.Message
		msg = &m
	}
	return approvers, nil, nil, msg
}

func remediationApprovalFlowApproversFromApproval(appr *apprent.Approval) ([]*entity.RemediationApprovalFlowApprover, *string, *time.Time) {
	approvers := make([]*entity.RemediationApprovalFlowApprover, 0)
	var operator *string
	var operatedAt *time.Time

	if appr == nil {
		return approvers, operator, operatedAt
	}

	for _, level := range appr.Levels {
		for _, node := range level.Nodes {
			for _, task := range node.Tasks {
				approvers = append(approvers, &entity.RemediationApprovalFlowApprover{
					Source: "approval",
					Type:   "user",
					ID:     task.User,
					Name:   task.User,
					Email:  task.User,
				})
				if task.Status == apprent.ApprovalTaskStatusApproved || task.Status == apprent.ApprovalTaskStatusRejected || task.Status == apprent.ApprovalTaskStatusDone {
					op := task.User
					operator = &op
					if task.EndAt != nil {
						tm := time.Unix(*task.EndAt, 0)
						operatedAt = &tm
					}
				}
			}
		}
	}

	return approvers, operator, operatedAt
}

func (s *svc) remediationApprovalFlowDefaultApprovers(ticket *entity.RemediationTicket, phase entity.RemediationTicketPhase) []*entity.RemediationApprovalFlowApprover {
	approvers := make([]*entity.RemediationApprovalFlowApprover, 0)
	if ticket == nil {
		return approvers
	}

	switch phase {
	case entity.RemediationTicketPhaseCreated, entity.RemediationTicketPhaseSucceed, entity.RemediationTicketPhaseClosed:
		return approvers
	default:
		if email, err := rmhelpers.GetResponsiblePersonEmail(context.Background(), &s.conf.ResponsiblePersons, ticket, phase); err == nil && email != "" {
			approvers = append(approvers, &entity.RemediationApprovalFlowApprover{
				Source: "configured",
				Type:   "user",
				ID:     email,
				Name:   email,
				Email:  email,
			})
		}
	}

	return approvers
}

func (s *svc) GenerateFinalDoc(ctx context.Context, ticketId uint64, forcedRegenerate bool) (string, error) {
	var docUrl string
	// transational
	txnErr := s.dbp.Transaction(ctx, func(ctx context.Context) error {
		ticket, err := s.repo.LoadTicket(ctx, ticketId)
		if err != nil {
			return fmt.Errorf("get ticket failed for ticket id %d: %w", ticketId, err)
		}

		if ticket.GeneratedDocURL != nil && len(*ticket.GeneratedDocURL) != 0 && !forcedRegenerate {
			return nil
		}

		if ticket.Phase == nil {
			return fmt.Errorf("ticket %d 's phase should not be nil", ticketId)
		}

		if *ticket.Phase != entity.RemediationTicketPhaseSucceed {
			return fmt.Errorf("ticket %d is not in succeed phase", ticketId)
		}

		// if ticket.ClusterID == nil {
		// 	return fmt.Errorf("cluster id is nil for ticket id %d", ticketId)
		// }

		var title string
		if ticket.ClusterID != nil {
			title = fmt.Sprintf("Remediation Ticket %d for Cluster %d", ticketId, *ticket.ClusterID)
		} else {
			title = fmt.Sprintf("Remediation Ticket %d", ticketId)
		}

		docCli := s.larkCliSet.Document
		docBuilder, err := docCli.NewDefaultDocumentBuilder(title)
		if err != nil {
			return fmt.Errorf("initiate document builder failed for ticket id %d: %w", ticketId, err)
		}

		generateDocContent(docBuilder, ticket)

		docId, err := docBuilder.Create(ctx)
		if err != nil {
			return fmt.Errorf("create lark doc failed for ticket id %d: %w", ticketId, err)
		}

		docUrl = docCli.GenerateURLFromDocumentID(docId)

		err = s.repo.PatchTicket(ctx, &entity.RemediationTicket{ID: ticketId, GeneratedDocURL: &docUrl})
		if err != nil {
			return fmt.Errorf("save docUrl failed for ticket id %d: %w", ticketId, err)
		}

		return nil
	})

	if txnErr != nil {
		return "", txnErr
	}

	return docUrl, nil
}
