package wecom

import (
	"context"
	"strings"
	"time"

	"github.com/hibiken/asynq"

	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/queue"
)

// ApprovalRecoveryTaskHandler 周期恢复结果未知审批并轮询未终态详情。
type ApprovalRecoveryTaskHandler struct {
	contexts *ApprovalContextRepository
	infos    *ApprovalInfoClient
	queue    *queue.Client
	now      func() time.Time
}

// NewApprovalRecoveryTaskHandler 创建企业微信审批主动恢复任务 Handler。
func NewApprovalRecoveryTaskHandler(contexts *ApprovalContextRepository, infos *ApprovalInfoClient, queueClient *queue.Client) *ApprovalRecoveryTaskHandler {
	return &ApprovalRecoveryTaskHandler{contexts: contexts, infos: infos, queue: queueClient, now: time.Now}
}

// Handle 扫描未终态和结果未知记录；结果未知只查询关联，绝不重新调用 applyevent。
func (h *ApprovalRecoveryTaskHandler) Handle(ctx context.Context, _ *asynq.Task) error {
	if h == nil || h.contexts == nil || h.infos == nil || h.queue == nil {
		return errors.New(errors.CodeServiceUnavailable, "企业微信审批主动恢复任务未配置")
	}
	now := h.now().UTC()
	if err := h.contexts.PromoteStaleSendingToUnknown(ctx, now.Add(-constants.WeComApprovalSendingLease)); err != nil {
		return err
	}
	if err := h.enqueuePendingSync(ctx, now); err != nil {
		return err
	}
	return h.recoverUnknown(ctx, now)
}

func (h *ApprovalRecoveryTaskHandler) enqueuePendingSync(ctx context.Context, now time.Time) error {
	records, err := h.contexts.ListPendingSync(ctx, now.Add(-constants.WeComApprovalPollingInterval), constants.WeComApprovalRecoveryBatchSize)
	if err != nil {
		return err
	}
	for _, record := range records {
		if err := h.enqueueDetailSync(ctx, record.ApplicationID, record.SPNo); err != nil {
			return err
		}
	}
	return nil
}

func (h *ApprovalRecoveryTaskHandler) recoverUnknown(ctx context.Context, now time.Time) error {
	cutoff := now.Add(-constants.WeComApprovalPollingInterval)
	records, err := h.contexts.ListUnknownRecovery(ctx, cutoff, constants.WeComApprovalUnknownRecoveryBatchSize)
	if err != nil {
		return err
	}
	for _, record := range records {
		claimed, err := h.contexts.ClaimUnknownRecovery(ctx, record.InstanceID, cutoff)
		if err != nil {
			return err
		}
		if !claimed {
			continue
		}
		spNo, err := h.contexts.FindSuccessfulSubmissionSPNo(ctx, record.InstanceID)
		if err != nil {
			return err
		}
		if spNo == "" {
			spNo, err = h.findUniqueSPNo(ctx, record, now)
			if err != nil {
				return err
			}
		}
		if spNo == "" {
			continue
		}
		recovered, err := h.contexts.RecoverSubmitted(ctx, record.InstanceID, spNo)
		if err != nil {
			return err
		}
		if recovered {
			if err := h.enqueueDetailSync(ctx, record.ApplicationID, spNo); err != nil {
				return err
			}
		}
	}
	return nil
}

// findUniqueSPNo 使用提交时间附近的固定窄窗口分页查询，并只接受唯一未关联候选。
func (h *ApprovalRecoveryTaskHandler) findUniqueSPNo(ctx context.Context, record ApprovalRecoveryRecord, now time.Time) (string, error) {
	startTime := record.SubmissionAttemptedAt.Add(-constants.WeComApprovalRecoveryWindow)
	endTime := record.SubmissionAttemptedAt.Add(constants.WeComApprovalRecoveryWindow)
	if endTime.After(now) {
		endTime = now
	}
	if !startTime.Before(endTime) {
		return "", nil
	}
	cursor := ""
	seenCursors := make(map[string]struct{})
	seenCandidates := make(map[string]struct{})
	unbound := make([]string, 0, 2)
	for {
		page, err := h.infos.List(ctx, ApprovalInfoQuery{
			ApplicationID: record.ApplicationID, StartTime: startTime, EndTime: endTime,
			TemplateID: record.TemplateID, CreatorUserID: record.CreatorUserID,
			Cursor: cursor, Size: constants.WeComApprovalInfoMaxPageSize,
		})
		if err != nil {
			return "", err
		}
		existing, err := h.contexts.ExistingSPNos(ctx, record.ApplicationID, page.SPNos)
		if err != nil {
			return "", err
		}
		for _, candidate := range page.SPNos {
			if _, seen := seenCandidates[candidate]; seen {
				continue
			}
			seenCandidates[candidate] = struct{}{}
			if _, exists := existing[candidate]; !exists {
				unbound = append(unbound, candidate)
				if len(unbound) > 1 {
					return "", nil
				}
			}
		}
		next := strings.TrimSpace(page.NextCursor)
		if next == "" {
			break
		}
		if _, exists := seenCursors[next]; exists {
			return "", errors.New(errors.CodeServiceUnavailable, "企业微信批量审批单号分页游标重复")
		}
		seenCursors[next] = struct{}{}
		cursor = next
	}
	if len(unbound) != 1 {
		return "", nil
	}
	return unbound[0], nil
}

func (h *ApprovalRecoveryTaskHandler) enqueueDetailSync(ctx context.Context, applicationID uint, spNo string) error {
	return h.queue.EnqueueTask(ctx, constants.TaskTypeWeComApprovalSync, ApprovalDetailSyncTask{
		ApplicationID: applicationID, SPNo: spNo, Source: constants.ApprovalSyncSourcePolling,
	})
}
