package com.ycwl.basic.puzzle.edge.task;

import cn.hutool.core.util.StrUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.fasterxml.jackson.core.type.TypeReference;
import com.ycwl.basic.model.pc.puzzle.entity.PuzzleWatermarkEntity;
import com.ycwl.basic.model.task.req.ClientStatusReqVo;
import com.ycwl.basic.puzzle.edge.dto.PuzzleEdgeRenderTaskDTO;
import com.ycwl.basic.puzzle.edge.dto.PuzzleEdgeTaskFailRequest;
import com.ycwl.basic.puzzle.edge.dto.PuzzleEdgeTaskSuccessRequest;
import com.ycwl.basic.puzzle.edge.dto.PuzzleEdgeUploadUrlsResponse;
import com.ycwl.basic.puzzle.edge.dto.PuzzleEdgeWorkerSyncRequest;
import com.ycwl.basic.puzzle.edge.dto.PuzzleEdgeWorkerSyncResponse;
import com.ycwl.basic.puzzle.edge.entity.PuzzleEdgeRenderTaskEntity;
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
import com.ycwl.basic.puzzle.mapper.PuzzleGenerationRecordMapper;
import com.ycwl.basic.puzzle.mapper.PuzzleWatermarkMapper;
import com.ycwl.basic.puzzle.repository.PuzzleRepository;
import com.ycwl.basic.service.pc.processor.PuzzleRelationProcessor;
import com.ycwl.basic.service.printer.PrinterService;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.utils.JacksonUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.scheduling.annotation.Scheduled;

import java.util.Date;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Puzzle 边缘渲染任务服务（中心端）
 * 负责：任务创建、任务拉取、签发上传URL、回报成功/失败落库
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PuzzleEdgeRenderTaskService {

    private static final int STATUS_PENDING = 0;
    private static final int STATUS_RUNNING = 1;
    private static final int STATUS_SUCCESS = 2;
    private static final int STATUS_FAIL = 3;

    /**
     * 任务类型：原始拼图（成功后更新 puzzle_generation_record）
     */
    public static final String TASK_TYPE_PUZZLE = "PUZZLE";
    /**
     * 任务类型：水印拼图（成功后写入 puzzle_watermark）
     */
    public static final String TASK_TYPE_WATERMARK = "WATERMARK";

    private static final int MAX_SYNC_TASKS = 5;
    private static final long LEASE_MILLIS = TimeUnit.SECONDS.toMillis(20);
    private static final long UPLOAD_URL_EXPIRE_MILLIS = TimeUnit.HOURS.toMillis(1);
    private static final int MAX_RETRY_ATTEMPTS = 3;

    private static final long TASK_CACHE_EXPIRE_HOURS = 6L;
    private static final long TASK_CACHE_MAX_SIZE = 20000L;
    private static final long WAIT_FUTURE_EXPIRE_MILLIS = TimeUnit.MINUTES.toMillis(10);

    /**
     * 任务等待结果
     */
    public static class TaskWaitResult {
        private final boolean success;
        private final String errorMessage;
        private final String imageUrl;

        private TaskWaitResult(boolean success, String errorMessage, String imageUrl) {
            this.success = success;
            this.errorMessage = errorMessage;
            this.imageUrl = imageUrl;
        }

        public static TaskWaitResult success(String imageUrl) {
            return new TaskWaitResult(true, null, imageUrl);
        }

        public static TaskWaitResult fail(String errorMessage) {
            return new TaskWaitResult(false, errorMessage, null);
        }

        public boolean isSuccess() {
            return success;
        }

        public String getErrorMessage() {
            return errorMessage;
        }

        public String getImageUrl() {
            return imageUrl;
        }
    }

    /**
     * 等待 future 的包装，包含创建时间用于过期清理
     */
    private static class WaitFutureEntry {
        final CompletableFuture<TaskWaitResult> future;
        final long createTimeMillis;

        WaitFutureEntry(CompletableFuture<TaskWaitResult> future) {
            this.future = future;
            this.createTimeMillis = System.currentTimeMillis();
        }

        boolean isExpired(long nowMillis) {
            return nowMillis - createTimeMillis > WAIT_FUTURE_EXPIRE_MILLIS;
        }
    }

    /**
     * 任务内存池（单实例、允许丢失）：仅用作 Worker 拉取与状态落地的中间态
     */
    private final Cache<Long, PuzzleEdgeRenderTaskEntity> taskCache = Caffeine.newBuilder()
            .expireAfterWrite(TASK_CACHE_EXPIRE_HOURS, TimeUnit.HOURS)
            .maximumSize(TASK_CACHE_MAX_SIZE)
            .build();

    private final AtomicLong taskIdSequence = new AtomicLong(System.currentTimeMillis());
    private final Object taskLock = new Object();

    /**
     * 任务等待 future 池：用于伪同步等待任务完成
     */
    private final ConcurrentHashMap<Long, WaitFutureEntry> waitFutures = new ConcurrentHashMap<>();

    private final PuzzleGenerationRecordMapper recordMapper;
    private final PuzzleWatermarkMapper puzzleWatermarkMapper;
    private final PuzzleRepository puzzleRepository;
    private final PrinterService printerService;
    private final PuzzleRelationProcessor puzzleRelationProcessor;

    /**
     * 固定的 workerId，用于标识通过 IP CIDR 验证的 worker
     * 由于 IP 验证已在拦截器层完成，此处不再区分具体 worker
     */
    private static final Long DEFAULT_WORKER_ID = 1L;

    public PuzzleEdgeWorkerSyncResponse sync(PuzzleEdgeWorkerSyncRequest req) {
        // IP 验证已在拦截器层完成，此处无需验证 accessKey
        Long workerId = DEFAULT_WORKER_ID;

        // 客户端状态上报（可选，不再关联具体 worker）
        ClientStatusReqVo clientStatus = req != null ? req.getClientStatus() : null;
        if (clientStatus != null) {
            log.debug("收到客户端状态上报: {}", clientStatus);
        }

        int maxTasks = req != null && req.getMaxTasks() != null ? req.getMaxTasks() : 1;
        if (maxTasks <= 0) {
            maxTasks = 1;
        }
        if (maxTasks > MAX_SYNC_TASKS) {
            maxTasks = MAX_SYNC_TASKS;
        }

        PuzzleEdgeWorkerSyncResponse resp = new PuzzleEdgeWorkerSyncResponse();
        for (int i = 0; i < maxTasks; i++) {
            PuzzleEdgeRenderTaskEntity task = claimOne(workerId);
            if (task == null) {
                break;
            }

            PuzzleEdgeRenderTaskDTO dto = toTaskDTOOrFail(task, workerId);
            if (dto != null) {
                resp.getTasks().add(dto);
            }
        }

        return resp;
    }

    public PuzzleEdgeUploadUrlsResponse getUploadUrls(Long taskId) {
        // IP 验证已在拦截器层完成，此处无需验证 accessKey
        Long workerId = DEFAULT_WORKER_ID;
        PuzzleEdgeRenderTaskEntity task = getAndCheckRunningTask(taskId, workerId);
        return buildUploadUrls(task);
    }

    public void taskSuccess(Long taskId, PuzzleEdgeTaskSuccessRequest req) {
        // IP 验证已在拦截器层完成，此处无需验证 accessKey
        Long workerId = DEFAULT_WORKER_ID;
        PuzzleEdgeRenderTaskEntity task = getAndCheckTaskOwned(taskId, workerId);
        if (task.getStatus() != null && task.getStatus() == STATUS_SUCCESS) {
            return;
        }
        if (task.getStatus() == null || task.getStatus() != STATUS_RUNNING) {
            throw new IllegalArgumentException("任务状态非法");
        }

        boolean updated = tryMarkSuccess(task, workerId);
        if (!updated) {
            throw new IllegalStateException("任务状态更新失败");
        }

        IStorageAdapter storage = StorageFactory.use();
        String resultImageUrl = storage.getUrl(task.getOriginalObjectKey());

        // 根据任务类型决定写入哪个表
        String taskType = task.getTaskType();
        if (TASK_TYPE_WATERMARK.equals(taskType)) {
            // 水印拼图任务：写入 puzzle_watermark 表
            handleWatermarkTaskSuccess(task, resultImageUrl);
        } else {
            // 原始拼图任务（默认）：更新 puzzle_generation_record 表
            handlePuzzleTaskSuccess(task, req, resultImageUrl);
        }

        // 通知等待方任务完成
        completeWaitFuture(taskId, TaskWaitResult.success(resultImageUrl));
    }

    /**
     * 处理原始拼图任务成功
     */
    private void handlePuzzleTaskSuccess(PuzzleEdgeRenderTaskEntity task,
                                          PuzzleEdgeTaskSuccessRequest req,
                                          String resultImageUrl) {
        PuzzleGenerationRecordEntity record = recordMapper.getById(task.getRecordId());
        if (record == null) {
            log.warn("边缘渲染任务回报成功，但生成记录不存在: taskId={}, recordId={}", task.getId(), task.getRecordId());
            return;
        }

        Long resultFileSize = req != null ? req.getResultFileSize() : null;
        Integer resultWidth = req != null ? req.getResultWidth() : null;
        Integer resultHeight = req != null ? req.getResultHeight() : null;
        Integer renderDurationMs = req != null ? req.getRenderDurationMs() : null;

        recordMapper.updateSuccess(
                record.getId(),
                resultImageUrl,
                resultFileSize,
                resultWidth,
                resultHeight,
                renderDurationMs
        );

        // 清除生成记录缓存（状态已更新）
        puzzleRepository.clearRecordCache(record.getId(), record.getFaceId());

        // 创建member_puzzle关联记录（使用INSERT IGNORE避免重复）
        puzzleRelationProcessor.createPuzzleRelation(
                record.getUserId(),
                record.getScenicId(),
                record.getFaceId(),
                record.getId()
        );

        PuzzleTemplateEntity template = puzzleRepository.getTemplateById(task.getTemplateId());
        if (template != null && template.getAutoAddPrint() != null && template.getAutoAddPrint() == 1) {
            try {
                Integer printRecordId = printerService.addUserPhotoFromPuzzle(
                        record.getUserId(),
                        record.getScenicId(),
                        record.getFaceId(),
                        resultImageUrl,
                        record.getId()  // 拼图记录ID，用于关联 puzzle_generation_record 表
                );
                log.info("自动添加到打印队列成功: recordId={}, printRecordId={}", record.getId(), printRecordId);
            } catch (Exception e) {
                log.error("自动添加到打印队列失败: recordId={}", record.getId(), e);
            }
        }
    }

    /**
     * 处理水印拼图任务成功
     */
    private void handleWatermarkTaskSuccess(PuzzleEdgeRenderTaskEntity task, String resultImageUrl) {
        PuzzleWatermarkEntity watermark = new PuzzleWatermarkEntity();
        watermark.setRecordId(task.getRecordId());
        watermark.setFaceId(task.getFaceId());
        watermark.setWatermarkType(task.getWatermarkType());
        watermark.setWatermarkUrl(resultImageUrl);

        try {
            puzzleWatermarkMapper.insert(watermark);
            log.info("水印拼图任务成功，已写入puzzle_watermark: taskId={}, recordId={}, watermarkType={}",
                    task.getId(), task.getRecordId(), task.getWatermarkType());
        } catch (Exception e) {
            log.error("水印拼图任务成功，但写入puzzle_watermark失败: taskId={}, recordId={}",
                    task.getId(), task.getRecordId(), e);
        }
    }

    public void taskFail(Long taskId, PuzzleEdgeTaskFailRequest req) {
        // IP 验证已在拦截器层完成，此处无需验证 accessKey
        Long workerId = DEFAULT_WORKER_ID;
        PuzzleEdgeRenderTaskEntity task = getAndCheckTaskOwned(taskId, workerId);
        if (task.getStatus() != null && task.getStatus() == STATUS_FAIL) {
            return;
        }
        if (task.getStatus() == null || task.getStatus() != STATUS_RUNNING) {
            throw new IllegalArgumentException("任务状态非法");
        }

        String errorMessage = req != null && StrUtil.isNotBlank(req.getErrorMessage())
                ? req.getErrorMessage()
                : "边缘渲染失败";

        boolean updated = tryMarkFail(task, workerId, errorMessage);
        if (!updated) {
            throw new IllegalStateException("任务状态更新失败");
        }
        recordMapper.updateFail(task.getRecordId(), errorMessage);

        // 清除生成记录缓存（状态已更新）
        puzzleRepository.clearRecordCache(task.getRecordId(), task.getFaceId());

        // 通知等待方任务失败
        completeWaitFuture(taskId, TaskWaitResult.fail(errorMessage));
    }

    /**
     * 运行超时自动失败并重试：
     * - task.status=RUNNING 且 leaseExpireTime 已过期：视为本次尝试超时
     * - attemptCount < MAX_RETRY_ATTEMPTS：重置为 PENDING 等待重试
     * - attemptCount >= MAX_RETRY_ATTEMPTS：最终失败并落库
     */
    @Scheduled(fixedDelay = 1000L)
    public void timeoutFailAndRetry() {
        List<Long> retryRecordIds = new ArrayList<>();
        Map<Long, String> failRecordMessages = new HashMap<>();
        Map<Long, String> failTaskMessages = new HashMap<>(); // taskId -> errorMessage
        Map<Long, Long> failRecordFaceIds = new HashMap<>(); // recordId -> faceId，用于缓存清除

        synchronized (taskLock) {
            long now = System.currentTimeMillis();
            for (PuzzleEdgeRenderTaskEntity task : taskCache.asMap().values()) {
                if (task == null || task.getId() == null || task.getStatus() == null) {
                    continue;
                }

                if (task.getStatus() != STATUS_RUNNING) {
                    continue;
                }

                Date leaseExpireTime = task.getLeaseExpireTime();
                if (leaseExpireTime == null || leaseExpireTime.getTime() >= now) {
                    continue;
                }

                int attemptCount = task.getAttemptCount() != null ? task.getAttemptCount() : 0;
                if (attemptCount >= MAX_RETRY_ATTEMPTS) {
                    String errorMessage = String.format("边缘渲染任务超时（%d秒），重试次数耗尽: attemptCount=%d",
                            TimeUnit.MILLISECONDS.toSeconds(LEASE_MILLIS), attemptCount);
                    log.warn("边缘渲染任务最终失败: taskId={}, recordId={}, {}", task.getId(), task.getRecordId(), errorMessage);

                    task.setStatus(STATUS_FAIL);
                    task.setWorkerId(null);
                    task.setLeaseExpireTime(null);
                    task.setErrorMessage(errorMessage);
                    task.setUpdateTime(new Date(now));
                    if (task.getRecordId() != null) {
                        failRecordMessages.put(task.getRecordId(), errorMessage);
                        failRecordFaceIds.put(task.getRecordId(), task.getFaceId());
                    }
                    // 记录需要通知的任务
                    failTaskMessages.put(task.getId(), errorMessage);
                    continue;
                }

                log.warn("边缘渲染任务超时，准备重试: taskId={}, recordId={}, attemptCount={}",
                        task.getId(), task.getRecordId(), attemptCount);

                task.setStatus(STATUS_PENDING);
                task.setWorkerId(null);
                task.setLeaseExpireTime(null);
                task.setErrorMessage("边缘渲染任务超时，等待重试");
                task.setUpdateTime(new Date(now));
                if (task.getRecordId() != null) {
                    retryRecordIds.add(task.getRecordId());
                }
            }
        }

        for (Long recordId : retryRecordIds) {
            incrementRecordRetryCount(recordId);
        }

        for (Map.Entry<Long, String> entry : failRecordMessages.entrySet()) {
            recordMapper.updateFail(entry.getKey(), entry.getValue());
            // 清除生成记录缓存
            Long faceId = failRecordFaceIds.get(entry.getKey());
            puzzleRepository.clearRecordCache(entry.getKey(), faceId);
        }

        // 通知等待方任务最终失败
        for (Map.Entry<Long, String> entry : failTaskMessages.entrySet()) {
            completeWaitFuture(entry.getKey(), TaskWaitResult.fail(entry.getValue()));
        }

        // 清理过期的等待 future
        cleanupExpiredWaitFutures();
    }

    /**
     * 清理过期的等待 future，防止内存泄漏
     */
    private void cleanupExpiredWaitFutures() {
        long now = System.currentTimeMillis();
        Iterator<Map.Entry<Long, WaitFutureEntry>> iterator = waitFutures.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Long, WaitFutureEntry> entry = iterator.next();
            if (entry.getValue().isExpired(now)) {
                entry.getValue().future.complete(TaskWaitResult.fail("等待超时（内部清理）"));
                iterator.remove();
            }
        }
    }

    private void incrementRecordRetryCount(Long recordId) {
        if (recordId == null) {
            return;
        }
        try {
            PuzzleGenerationRecordEntity record = recordMapper.getById(recordId);
            if (record == null) {
                return;
            }
            int currentRetryCount = record.getRetryCount() != null ? record.getRetryCount() : 0;
            PuzzleGenerationRecordEntity update = new PuzzleGenerationRecordEntity();
            update.setId(recordId);
            update.setRetryCount(currentRetryCount + 1);
            recordMapper.update(update);
        } catch (Exception e) {
            log.warn("更新生成记录重试次数失败: recordId={}, error={}", recordId, e.getMessage());
        }
    }

    /**
     * 创建边缘渲染任务（供中心业务侧调用）
     */
    public Long createRenderTask(PuzzleGenerationRecordEntity record,
                                 PuzzleTemplateEntity template,
                                 List<PuzzleElementEntity> sortedElements,
                                 Map<String, String> finalDynamicData,
                                 String outputFormat,
                                 Integer quality) {
        if (record == null || record.getId() == null) {
            throw new IllegalArgumentException("record不能为空");
        }
        if (template == null || template.getId() == null) {
            throw new IllegalArgumentException("template不能为空");
        }
        if (sortedElements == null) {
            sortedElements = List.of();
        }
        if (finalDynamicData == null) {
            finalDynamicData = Map.of();
        }

        String normalizedFormat = normalizeOutputFormat(outputFormat);
        Integer outputQuality = quality != null ? quality : 90;
        String ext = "PNG".equals(normalizedFormat) ? "png" : "jpeg";
        String fileName = UUID.randomUUID().toString().replace("-", "") + "." + ext;

        String originalObjectKey = String.format("puzzle/%s/%s", template.getCode(), fileName);

        Map<String, Object> payload = new HashMap<>();
        payload.put("recordId", record.getId());

        Map<String, Object> templatePayload = new HashMap<>();
        templatePayload.put("id", template.getId());
        templatePayload.put("code", template.getCode());
        templatePayload.put("canvasWidth", template.getCanvasWidth());
        templatePayload.put("canvasHeight", template.getCanvasHeight());
        templatePayload.put("backgroundType", template.getBackgroundType());
        templatePayload.put("backgroundColor", template.getBackgroundColor());
        templatePayload.put("backgroundImage", template.getBackgroundImage());
        payload.put("template", templatePayload);

        List<Map<String, Object>> elementPayloadList = new ArrayList<>();
        for (PuzzleElementEntity e : sortedElements) {
            Map<String, Object> elementPayload = new HashMap<>();
            elementPayload.put("id", e.getId());
            elementPayload.put("type", e.getElementType());
            elementPayload.put("key", e.getElementKey());
            elementPayload.put("name", e.getElementName());
            elementPayload.put("x", e.getXPosition());
            elementPayload.put("y", e.getYPosition());
            elementPayload.put("width", e.getWidth());
            elementPayload.put("height", e.getHeight());
            elementPayload.put("zIndex", e.getZIndex());
            elementPayload.put("rotation", e.getRotation());
            elementPayload.put("opacity", e.getOpacity());
            elementPayload.put("config", e.getConfig());
            elementPayloadList.add(elementPayload);
        }
        payload.put("elements", elementPayloadList);
        payload.put("dynamicData", finalDynamicData);

        Map<String, Object> outputPayload = new HashMap<>();
        outputPayload.put("format", normalizedFormat);
        outputPayload.put("quality", outputQuality);
        payload.put("output", outputPayload);

        PuzzleEdgeRenderTaskEntity task = new PuzzleEdgeRenderTaskEntity();
        task.setRecordId(record.getId());
        task.setTemplateId(template.getId());
        task.setTemplateCode(template.getCode());
        task.setScenicId(record.getScenicId());
        task.setFaceId(record.getFaceId());
        task.setTaskType(TASK_TYPE_PUZZLE);
        task.setWatermarkType(null);
        task.setContentHash(record.getContentHash());
        task.setStatus(STATUS_PENDING);
        task.setAttemptCount(0);
        task.setOutputFormat(normalizedFormat);
        task.setOutputQuality(outputQuality);
        task.setOriginalObjectKey(originalObjectKey);
        task.setCroppedObjectKey(null);
        task.setPayloadJson(JacksonUtil.toJson(payload));

        Long taskId = taskIdSequence.incrementAndGet();
        Date now = new Date();
        task.setId(taskId);
        task.setCreateTime(now);
        task.setUpdateTime(now);

        taskCache.put(taskId, task);
        return taskId;
    }

    /**
     * 创建水印拼图边缘渲染任务（供中心业务侧调用）
     * 成功后将结果写入 puzzle_watermark 表
     *
     * @param recordId          原始拼图生成记录ID
     * @param faceId            人脸ID（可选）
     * @param watermarkType     水印类型（如 print、free_download）
     * @param template          模板配置
     * @param sortedElements    元素列表（按z-index排序）
     * @param finalDynamicData  动态数据
     * @param outputFormat      输出格式
     * @param quality           输出质量
     * @return 任务ID
     */
    public Long createWatermarkRenderTask(Long recordId,
                                           Long faceId,
                                           String watermarkType,
                                           PuzzleTemplateEntity template,
                                           List<PuzzleElementEntity> sortedElements,
                                           Map<String, String> finalDynamicData,
                                           String outputFormat,
                                           Integer quality) {
        if (recordId == null) {
            throw new IllegalArgumentException("recordId不能为空");
        }
        if (StrUtil.isBlank(watermarkType)) {
            throw new IllegalArgumentException("watermarkType不能为空");
        }
        if (template == null || template.getId() == null) {
            throw new IllegalArgumentException("template不能为空");
        }
        if (sortedElements == null) {
            sortedElements = List.of();
        }
        if (finalDynamicData == null) {
            finalDynamicData = Map.of();
        }

        String normalizedFormat = normalizeOutputFormat(outputFormat);
        Integer outputQuality = quality != null ? quality : 90;
        String ext = "PNG".equals(normalizedFormat) ? "png" : "jpeg";
        String fileName = UUID.randomUUID().toString().replace("-", "") + "." + ext;

        // 水印拼图使用单独的目录
        String originalObjectKey = String.format("puzzle_watermark/%s/%s/%s", template.getCode(), watermarkType, fileName);

        Map<String, Object> payload = new HashMap<>();
        payload.put("recordId", recordId);
        payload.put("watermarkType", watermarkType);

        Map<String, Object> templatePayload = new HashMap<>();
        templatePayload.put("id", template.getId());
        templatePayload.put("code", template.getCode());
        templatePayload.put("canvasWidth", template.getCanvasWidth());
        templatePayload.put("canvasHeight", template.getCanvasHeight());
        templatePayload.put("backgroundType", template.getBackgroundType());
        templatePayload.put("backgroundColor", template.getBackgroundColor());
        templatePayload.put("backgroundImage", template.getBackgroundImage());
        payload.put("template", templatePayload);

        List<Map<String, Object>> elementPayloadList = new ArrayList<>();
        for (PuzzleElementEntity e : sortedElements) {
            Map<String, Object> elementPayload = new HashMap<>();
            elementPayload.put("id", e.getId());
            elementPayload.put("type", e.getElementType());
            elementPayload.put("key", e.getElementKey());
            elementPayload.put("name", e.getElementName());
            elementPayload.put("x", e.getXPosition());
            elementPayload.put("y", e.getYPosition());
            elementPayload.put("width", e.getWidth());
            elementPayload.put("height", e.getHeight());
            elementPayload.put("zIndex", e.getZIndex());
            elementPayload.put("rotation", e.getRotation());
            elementPayload.put("opacity", e.getOpacity());
            elementPayload.put("config", e.getConfig());
            elementPayloadList.add(elementPayload);
        }
        payload.put("elements", elementPayloadList);
        payload.put("dynamicData", finalDynamicData);

        Map<String, Object> outputPayload = new HashMap<>();
        outputPayload.put("format", normalizedFormat);
        outputPayload.put("quality", outputQuality);
        payload.put("output", outputPayload);

        PuzzleEdgeRenderTaskEntity task = new PuzzleEdgeRenderTaskEntity();
        task.setRecordId(recordId);
        task.setTemplateId(template.getId());
        task.setTemplateCode(template.getCode());
        task.setScenicId(template.getScenicId());
        task.setFaceId(faceId);
        task.setTaskType(TASK_TYPE_WATERMARK);
        task.setWatermarkType(watermarkType);
        task.setContentHash(null); // 水印任务不需要内容哈希去重
        task.setStatus(STATUS_PENDING);
        task.setAttemptCount(0);
        task.setOutputFormat(normalizedFormat);
        task.setOutputQuality(outputQuality);
        task.setOriginalObjectKey(originalObjectKey);
        task.setCroppedObjectKey(null);
        task.setPayloadJson(JacksonUtil.toJson(payload));

        Long taskId = taskIdSequence.incrementAndGet();
        Date now = new Date();
        task.setId(taskId);
        task.setCreateTime(now);
        task.setUpdateTime(now);

        taskCache.put(taskId, task);
        return taskId;
    }

    /**
     * 注册任务等待，返回用于等待的 CompletableFuture
     * 调用方应在 createRenderTask 之后立即调用此方法
     *
     * @param taskId 任务ID
     * @return CompletableFuture，可用于同步等待或异步处理
     */
    public CompletableFuture<TaskWaitResult> registerWait(Long taskId) {
        if (taskId == null) {
            CompletableFuture<TaskWaitResult> failedFuture = new CompletableFuture<>();
            failedFuture.complete(TaskWaitResult.fail("taskId不能为空"));
            return failedFuture;
        }

        CompletableFuture<TaskWaitResult> future = new CompletableFuture<>();
        waitFutures.put(taskId, new WaitFutureEntry(future));
        return future;
    }

    /**
     * 伪同步等待任务完成
     * 阻塞当前线程直到任务成功、失败或超时
     *
     * @param taskId 任务ID
     * @param timeoutMs 超时时间（毫秒）
     * @return 任务结果，包含成功/失败状态和相关信息
     */
    public TaskWaitResult waitForTask(Long taskId, long timeoutMs) {
        if (taskId == null) {
            return TaskWaitResult.fail("taskId不能为空");
        }

        // 检查任务是否已完成
        PuzzleEdgeRenderTaskEntity task = taskCache.getIfPresent(taskId);
        if (task != null) {
            if (task.getStatus() != null && task.getStatus() == STATUS_SUCCESS) {
                return buildSuccessResult(task);
            }
            if (task.getStatus() != null && task.getStatus() == STATUS_FAIL) {
                return TaskWaitResult.fail(task.getErrorMessage());
            }
        }

        // 获取或创建等待 future
        WaitFutureEntry entry = waitFutures.computeIfAbsent(taskId, k -> new WaitFutureEntry(new CompletableFuture<>()));

        try {
            return entry.future.get(timeoutMs, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            waitFutures.remove(taskId);
            return TaskWaitResult.fail("等待任务超时");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            waitFutures.remove(taskId);
            return TaskWaitResult.fail("等待被中断");
        } catch (ExecutionException e) {
            waitFutures.remove(taskId);
            return TaskWaitResult.fail("等待出错: " + e.getCause().getMessage());
        }
    }

    /**
     * 创建任务并同步等待结果（便捷方法）
     */
    public TaskWaitResult createAndWait(PuzzleGenerationRecordEntity record,
                                        PuzzleTemplateEntity template,
                                        List<PuzzleElementEntity> sortedElements,
                                        Map<String, String> finalDynamicData,
                                        String outputFormat,
                                        Integer quality,
                                        long timeoutMs) {
        Long taskId = createRenderTask(record, template, sortedElements, finalDynamicData, outputFormat, quality);
        registerWait(taskId);
        return waitForTask(taskId, timeoutMs);
    }

    private TaskWaitResult buildSuccessResult(PuzzleEdgeRenderTaskEntity task) {
        IStorageAdapter storage = StorageFactory.use();
        String imageUrl = storage.getUrl(task.getOriginalObjectKey());
        return TaskWaitResult.success(imageUrl);
    }

    /**
     * 完成任务等待（内部调用）
     */
    private void completeWaitFuture(Long taskId, TaskWaitResult result) {
        WaitFutureEntry entry = waitFutures.remove(taskId);
        if (entry != null && entry.future != null) {
            entry.future.complete(result);
        }
    }

    private PuzzleEdgeRenderTaskDTO toTaskDTOOrFail(PuzzleEdgeRenderTaskEntity task, Long workerId) {
        try {
            PuzzleEdgeRenderTaskDTO dto = new PuzzleEdgeRenderTaskDTO();
            dto.setId(task.getId());
            dto.setRecordId(task.getRecordId());
            dto.setTemplateId(task.getTemplateId());
            dto.setTemplateCode(task.getTemplateCode());
            dto.setScenicId(task.getScenicId());
            dto.setFaceId(task.getFaceId());
            dto.setAttemptCount(task.getAttemptCount());
            dto.setOutputFormat(task.getOutputFormat());
            dto.setOutputQuality(task.getOutputQuality());
            dto.setPayload(JacksonUtil.fromJson(task.getPayloadJson(), new TypeReference<Map<String, Object>>() {}));
            dto.setUpload(buildUploadUrls(task));
            return dto;
        } catch (Exception e) {
            String errorMessage = "任务数据组装失败: " + e.getMessage();
            log.error("边缘渲染任务组装失败: taskId={}, recordId={}", task.getId(), task.getRecordId(), e);
            tryMarkFail(task, workerId, errorMessage);
            recordMapper.updateFail(task.getRecordId(), errorMessage);
            return null;
        }
    }

    private PuzzleEdgeUploadUrlsResponse buildUploadUrls(PuzzleEdgeRenderTaskEntity task) {
        String outputFormat = normalizeOutputFormat(task.getOutputFormat());
        String contentType = resolveContentType(outputFormat);
        Date expireAt = new Date(System.currentTimeMillis() + UPLOAD_URL_EXPIRE_MILLIS);

        IStorageAdapter storage = StorageFactory.use();

        PuzzleEdgeUploadUrlsResponse resp = new PuzzleEdgeUploadUrlsResponse();
        resp.setExpireAt(expireAt);

        Map<String, String> headers = Map.of("Content-Type", contentType);

        PuzzleEdgeUploadUrlsResponse.UploadTarget original = new PuzzleEdgeUploadUrlsResponse.UploadTarget();
        original.setMethod("PUT");
        original.setHeaders(headers);
        original.setPublicUrl(storage.getUrl(task.getOriginalObjectKey()));
        original.setUrl(storage.getUrlForUpload(expireAt, contentType, task.getOriginalObjectKey()));
        resp.setOriginal(original);

        if (StrUtil.isNotBlank(task.getCroppedObjectKey())) {
            PuzzleEdgeUploadUrlsResponse.UploadTarget cropped = new PuzzleEdgeUploadUrlsResponse.UploadTarget();
            cropped.setMethod("PUT");
            cropped.setHeaders(headers);
            cropped.setPublicUrl(storage.getUrl(task.getCroppedObjectKey()));
            cropped.setUrl(storage.getUrlForUpload(expireAt, contentType, task.getCroppedObjectKey()));
            resp.setCropped(cropped);
        }

        return resp;
    }

    private PuzzleEdgeRenderTaskEntity claimOne(Long workerId) {
        synchronized (taskLock) {
            long now = System.currentTimeMillis();

            Long selectedTaskId = null;
            for (Map.Entry<Long, PuzzleEdgeRenderTaskEntity> entry : taskCache.asMap().entrySet()) {
                PuzzleEdgeRenderTaskEntity task = entry.getValue();
                if (!isClaimable(task, now)) {
                    continue;
                }

                Long id = entry.getKey();
                if (selectedTaskId == null || id < selectedTaskId) {
                    selectedTaskId = id;
                }
            }

            if (selectedTaskId == null) {
                return null;
            }

            PuzzleEdgeRenderTaskEntity task = taskCache.getIfPresent(selectedTaskId);
            if (!isClaimable(task, now)) {
                return null;
            }

            Date leaseExpireTime = new Date(now + LEASE_MILLIS);
            task.setWorkerId(workerId);
            task.setStatus(STATUS_RUNNING);
            task.setLeaseExpireTime(leaseExpireTime);
            task.setAttemptCount((task.getAttemptCount() != null ? task.getAttemptCount() : 0) + 1);
            task.setUpdateTime(new Date(now));
            return task;
        }
    }

    private PuzzleEdgeRenderTaskEntity getAndCheckRunningTask(Long taskId, Long workerId) {
        if (taskId == null) {
            throw new IllegalArgumentException("taskId不能为空");
        }
        synchronized (taskLock) {
            PuzzleEdgeRenderTaskEntity task = taskCache.getIfPresent(taskId);
            if (task == null) {
                throw new IllegalArgumentException("任务不存在");
            }
            if (task.getStatus() == null || task.getStatus() != STATUS_RUNNING) {
                throw new IllegalArgumentException("任务状态非法");
            }
            if (task.getWorkerId() == null || !task.getWorkerId().equals(workerId)) {
                throw new IllegalArgumentException("任务不属于当前worker");
            }
            return task;
        }
    }

    private PuzzleEdgeRenderTaskEntity getAndCheckTaskOwned(Long taskId, Long workerId) {
        if (taskId == null) {
            throw new IllegalArgumentException("taskId不能为空");
        }
        synchronized (taskLock) {
            PuzzleEdgeRenderTaskEntity task = taskCache.getIfPresent(taskId);
            if (task == null) {
                throw new IllegalArgumentException("任务不存在");
            }
            if (task.getWorkerId() == null || !task.getWorkerId().equals(workerId)) {
                throw new IllegalArgumentException("任务不属于当前worker");
            }
            return task;
        }
    }

    private boolean tryMarkSuccess(PuzzleEdgeRenderTaskEntity task, Long workerId) {
        synchronized (taskLock) {
            if (task == null || task.getId() == null) {
                return false;
            }
            if (task.getStatus() == null || task.getStatus() != STATUS_RUNNING) {
                return false;
            }
            if (task.getWorkerId() == null || !task.getWorkerId().equals(workerId)) {
                return false;
            }

            task.setStatus(STATUS_SUCCESS);
            task.setLeaseExpireTime(null);
            task.setErrorMessage(null);
            task.setUpdateTime(new Date());
            return true;
        }
    }

    private boolean tryMarkFail(PuzzleEdgeRenderTaskEntity task, Long workerId, String errorMessage) {
        synchronized (taskLock) {
            if (task == null || task.getId() == null) {
                return false;
            }
            if (task.getStatus() == null || task.getStatus() != STATUS_RUNNING) {
                return false;
            }
            if (task.getWorkerId() == null || !task.getWorkerId().equals(workerId)) {
                return false;
            }

            task.setStatus(STATUS_FAIL);
            task.setLeaseExpireTime(null);
            task.setErrorMessage(errorMessage);
            task.setUpdateTime(new Date());
            return true;
        }
    }

    private boolean isClaimable(PuzzleEdgeRenderTaskEntity task, long nowMillis) {
        if (task == null || task.getId() == null || task.getStatus() == null) {
            return false;
        }
        if (task.getStatus() == STATUS_PENDING) {
            int attemptCount = task.getAttemptCount() != null ? task.getAttemptCount() : 0;
            return attemptCount < MAX_RETRY_ATTEMPTS;
        }
        if (task.getStatus() != STATUS_RUNNING) {
            return false;
        }

        Date leaseExpireTime = task.getLeaseExpireTime();
        if (leaseExpireTime == null || leaseExpireTime.getTime() >= nowMillis) {
            return false;
        }

        int attemptCount = task.getAttemptCount() != null ? task.getAttemptCount() : 0;
        return attemptCount < MAX_RETRY_ATTEMPTS;
    }

    private String normalizeOutputFormat(String format) {
        String outputFormat = StrUtil.isNotBlank(format) ? format.toUpperCase() : "PNG";
        if ("JPG".equals(outputFormat)) {
            outputFormat = "JPEG";
        }
        if (!"PNG".equals(outputFormat) && !"JPEG".equals(outputFormat)) {
            outputFormat = "PNG";
        }
        return outputFormat;
    }

    private String resolveContentType(String outputFormat) {
        return "PNG".equals(outputFormat) ? "image/png" : "image/jpeg";
    }
}
