package com.nanri.aiimage.modules.deletebrand.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nanri.aiimage.common.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;

@Service
@RequiredArgsConstructor
public class DeleteBrandTaskCacheService {

    public static final String PHASE_CRAWLING = "crawling";
    public static final String PHASE_ASSEMBLING = "assembling";
    public static final String PHASE_UPLOADING = "uploading";
    public static final String PHASE_FAILED = "failed";

    private static final long PAYLOAD_TTL_HOURS = 24;
    private static final long LOCAL_PARSED_PAYLOAD_CACHE_MILLIS = 3000;

    private final StringRedisTemplate stringRedisTemplate;
    private final ObjectMapper objectMapper;
    private final ConcurrentHashMap<Long, LocalParsedPayloadCacheEntry> parsedPayloadLocalCache = new ConcurrentHashMap<>();

    public void saveParsedPayload(Long taskId, Object payload) {
        try {
            stringRedisTemplate.opsForValue().set(buildPayloadKey(taskId), objectMapper.writeValueAsString(payload), Duration.ofHours(PAYLOAD_TTL_HOURS));
            parsedPayloadLocalCache.put(taskId, new LocalParsedPayloadCacheEntry(System.currentTimeMillis(), payload));
        } catch (Exception ex) {
            throw new BusinessException("暂存删除品牌原始数据失败");
        }
    }

    public <T> T getParsedPayload(Long taskId, TypeReference<T> typeReference) {
        if (taskId == null || taskId <= 0) {
            return null;
        }

        long now = System.currentTimeMillis();
        LocalParsedPayloadCacheEntry cached = parsedPayloadLocalCache.get(taskId);
        if (cached != null && cached.isFresh(now) && cached.value() != null) {
            try {
                return objectMapper.convertValue(cached.value(), typeReference);
            } catch (Exception ignored) {
                // fallthrough to redis
            }
        }

        String raw = stringRedisTemplate.opsForValue().get(buildPayloadKey(taskId));
        if (raw == null || raw.isBlank()) {
            parsedPayloadLocalCache.remove(taskId);
            return null;
        }
        try {
            T parsed = objectMapper.readValue(raw, typeReference);
            parsedPayloadLocalCache.put(taskId, new LocalParsedPayloadCacheEntry(now, parsed));
            return parsed;
        } catch (Exception ex) {
            throw new BusinessException("读取删除品牌原始数据失败");
        }
    }

    public void saveProgress(Long taskId, java.util.Map<String, String> values) {
        String key = buildProgressKey(taskId);
        stringRedisTemplate.opsForHash().putAll(key, values);
        stringRedisTemplate.expire(key, Duration.ofHours(PAYLOAD_TTL_HOURS));
    }

    public java.util.Map<Object, Object> getProgress(Long taskId) {
        return stringRedisTemplate.opsForHash().entries(buildProgressKey(taskId));
    }

    public java.util.Map<Long, java.util.Map<Object, Object>> getProgressBatch(java.util.List<Long> taskIds) {
        java.util.Map<Long, java.util.Map<Object, Object>> result = new java.util.LinkedHashMap<>();
        if (taskIds == null || taskIds.isEmpty()) {
            return result;
        }

        java.util.List<Long> normalizedTaskIds = taskIds.stream()
                .filter(taskId -> taskId != null && taskId > 0)
                .distinct()
                .toList();
        if (normalizedTaskIds.isEmpty()) {
            return result;
        }

        java.util.List<Object> pipelineResults = stringRedisTemplate.executePipelined((org.springframework.data.redis.core.RedisCallback<Object>) connection -> {
            org.springframework.data.redis.serializer.RedisSerializer<String> serializer = stringRedisTemplate.getStringSerializer();
            for (Long taskId : normalizedTaskIds) {
                connection.hGetAll(serializer.serialize(buildProgressKey(taskId)));
            }
            return null;
        });

        for (int i = 0; i < normalizedTaskIds.size(); i++) {
            Long taskId = normalizedTaskIds.get(i);
            Object raw = pipelineResults != null && i < pipelineResults.size() ? pipelineResults.get(i) : null;
            if (raw instanceof java.util.Map<?, ?> rawMap) {
                java.util.Map<Object, Object> converted = new java.util.LinkedHashMap<>();
                for (java.util.Map.Entry<?, ?> entry : rawMap.entrySet()) {
                    converted.put(entry.getKey(), entry.getValue());
                }
                result.put(taskId, converted);
            } else {
                result.put(taskId, java.util.Collections.emptyMap());
            }
        }
        return result;
    }

    public boolean hasSameResultChunk(Long taskId, String fileIdentity, Integer chunkIndex, Object chunk) {
        if (chunkIndex == null || chunkIndex <= 0) {
            throw new BusinessException("chunkIndex 不合法");
        }
        if (fileIdentity == null || fileIdentity.isBlank()) {
            throw new BusinessException("fileIdentity 不能为空");
        }
        String field = fileIdentity.trim() + "#" + chunkIndex;
        String chunkJson;
        try {
            chunkJson = objectMapper.writeValueAsString(chunk);
        } catch (Exception ex) {
            throw new BusinessException("读取删除品牌结果分片失败");
        }
        Object existing = stringRedisTemplate.opsForHash().get(buildResultChunksKey(taskId), field);
        if (chunkJson.equals(existing)) {
            stringRedisTemplate.expire(buildResultChunksKey(taskId), Duration.ofHours(PAYLOAD_TTL_HOURS));
            return true;
        }
        return false;
    }

    public boolean mergeResultChunk(Long taskId, String fileIdentity, Integer chunkIndex, Object chunk) {
        String resultKey = buildResultChunksKey(taskId);
        if (chunkIndex == null || chunkIndex <= 0) {
            throw new BusinessException("chunkIndex 不合法");
        }
        if (fileIdentity == null || fileIdentity.isBlank()) {
            throw new BusinessException("fileIdentity 不能为空");
        }
        String field = fileIdentity.trim() + "#" + chunkIndex;
        String chunkJson;
        try {
            chunkJson = objectMapper.writeValueAsString(chunk);
        } catch (Exception ex) {
            throw new BusinessException("暂存删除品牌结果分片失败");
        }
        Object existing = stringRedisTemplate.opsForHash().get(resultKey, field);
        if (chunkJson.equals(existing)) {
            stringRedisTemplate.expire(resultKey, Duration.ofHours(PAYLOAD_TTL_HOURS));
            return false;
        }
        stringRedisTemplate.opsForHash().put(resultKey, field, chunkJson);
        stringRedisTemplate.expire(resultKey, Duration.ofHours(PAYLOAD_TTL_HOURS));
        return true;
    }

    public java.util.Map<String, java.util.List<String>> groupResultChunkJsonByFile(Long taskId) {
        java.util.Map<Object, Object> stored = stringRedisTemplate.opsForHash().entries(buildResultChunksKey(taskId));
        java.util.Map<String, java.util.List<String>> grouped = new java.util.LinkedHashMap<>();
        for (java.util.Map.Entry<Object, Object> entry : stored.entrySet()) {
            if (!(entry.getKey() instanceof String field) || !(entry.getValue() instanceof String raw) || raw.isBlank()) {
                continue;
            }
            int sep = field.lastIndexOf('#');
            if (sep <= 0) {
                continue;
            }
            String fileIdentity = field.substring(0, sep);
            grouped.computeIfAbsent(fileIdentity, ignored -> new java.util.ArrayList<>()).add(raw);
        }
        return grouped;
    }

    public void delete(Long taskId) {
        parsedPayloadLocalCache.remove(taskId);
        stringRedisTemplate.delete(buildPayloadKey(taskId));
        stringRedisTemplate.delete(buildProgressKey(taskId));
        stringRedisTemplate.delete(buildResultChunksKey(taskId));
        stringRedisTemplate.delete(buildTaskEntityKey(taskId));
    }

    public void saveTaskCache(com.nanri.aiimage.modules.task.model.entity.FileTaskEntity task) {
        if (task == null || task.getId() == null) return;
        try {
            stringRedisTemplate.opsForValue().set(buildTaskEntityKey(task.getId()), objectMapper.writeValueAsString(task), Duration.ofHours(PAYLOAD_TTL_HOURS));
        } catch (Exception ignored) {
        }
    }

    public java.util.Map<Long, com.nanri.aiimage.modules.task.model.entity.FileTaskEntity> getTaskCacheBatch(java.util.List<Long> taskIds) {
        java.util.Map<Long, com.nanri.aiimage.modules.task.model.entity.FileTaskEntity> result = new java.util.LinkedHashMap<>();
        if (taskIds == null || taskIds.isEmpty()) return result;

        java.util.List<Long> normalized = taskIds.stream().filter(id -> id != null && id > 0).distinct().toList();
        if (normalized.isEmpty()) return result;

        java.util.List<String> keys = normalized.stream().map(this::buildTaskEntityKey).toList();
        java.util.List<String> values = stringRedisTemplate.opsForValue().multiGet(keys);

        for (int i = 0; i < normalized.size(); i++) {
            Long taskId = normalized.get(i);
            String val = values != null && i < values.size() ? values.get(i) : null;
            if (val != null && !val.isBlank()) {
                try {
                    result.put(taskId, objectMapper.readValue(val, com.nanri.aiimage.modules.task.model.entity.FileTaskEntity.class));
                } catch (Exception ignored) {
                }
            }
        }
        return result;
    }

    private String buildTaskEntityKey(Long taskId) {
        return "delete-brand:task:entity:" + taskId;
    }

    private record LocalParsedPayloadCacheEntry(long cachedAtMillis, Object value) {
        private boolean isFresh(long now) {
            return now - cachedAtMillis <= LOCAL_PARSED_PAYLOAD_CACHE_MILLIS;
        }
    }

    private String buildPayloadKey(Long taskId) {
        return "delete-brand:task:parsed-payload:" + taskId;
    }

    private String buildProgressKey(Long taskId) {
        return "delete-brand:task:progress:" + taskId;
    }

    private String buildResultChunksKey(Long taskId) {
        return "delete-brand:task:result-chunks:" + taskId;
    }
}
