package cn.iocoder.yudao.module.energy.service.record;

import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.energy.controller.admin.record.vo.HydrogenRecordImportPreviewVO;
import cn.iocoder.yudao.module.energy.controller.admin.record.vo.HydrogenRecordImportProgressVO;
import cn.iocoder.yudao.module.energy.controller.admin.record.vo.HydrogenRecordImportVO;
import cn.iocoder.yudao.module.energy.controller.admin.record.vo.HydrogenRecordPageReqVO;
import cn.iocoder.yudao.module.energy.controller.admin.record.vo.HydrogenRecordSaveReqVO;
import cn.iocoder.yudao.module.energy.convert.record.HydrogenRecordConvert;
import cn.iocoder.yudao.module.energy.dal.dataobject.record.EnergyHydrogenRecordDO;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.energy.dal.mysql.record.EnergyHydrogenRecordMapper;
import cn.iocoder.yudao.module.energy.enums.MatchStatusEnum;
import cn.iocoder.yudao.module.energy.enums.SourceTypeEnum;
import cn.iocoder.yudao.module.energy.event.RecordImportedEvent;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.energy.enums.ErrorCodeConstants.*;

@Service
@Validated
@Slf4j
public class HydrogenRecordServiceImpl implements HydrogenRecordService {

    private static final String REDIS_KEY_PREVIEW = "energy:import:preview:";
    private static final String REDIS_KEY_PROGRESS = "energy:import:progress:";
    private static final long PREVIEW_TTL_MINUTES = 15L;

    @Resource
    private EnergyHydrogenRecordMapper hydrogenRecordMapper;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Long createRecord(HydrogenRecordSaveReqVO createReqVO) {
        EnergyHydrogenRecordDO record = HydrogenRecordConvert.INSTANCE.convert(createReqVO);
        record.setSourceType(SourceTypeEnum.WEB.getType());
        record.setMatchStatus(MatchStatusEnum.UNMATCHED.getStatus());
        hydrogenRecordMapper.insert(record);
        return record.getId();
    }

    @Override
    public void updateRecord(HydrogenRecordSaveReqVO updateReqVO) {
        validateRecordExists(updateReqVO.getId());
        EnergyHydrogenRecordDO updateObj = HydrogenRecordConvert.INSTANCE.convert(updateReqVO);
        hydrogenRecordMapper.updateById(updateObj);
    }

    @Override
    public void deleteRecord(Long id) {
        validateRecordExists(id);
        hydrogenRecordMapper.deleteById(id);
    }

    @Override
    public EnergyHydrogenRecordDO getRecord(Long id) {
        return hydrogenRecordMapper.selectById(id);
    }

    @Override
    public PageResult<EnergyHydrogenRecordDO> getRecordPage(HydrogenRecordPageReqVO pageReqVO) {
        return hydrogenRecordMapper.selectPage(pageReqVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Integer> importRecords(Long stationId, List<HydrogenRecordImportVO> list, String batchNo) {
        if (list == null || list.isEmpty()) {
            throw exception(HYDROGEN_RECORD_IMPORT_EMPTY);
        }
        int successCount = 0;
        int failCount = 0;
        List<Long> recordIds = new ArrayList<>();
        for (HydrogenRecordImportVO importVO : list) {
            try {
                EnergyHydrogenRecordDO record = EnergyHydrogenRecordDO.builder()
                        .stationId(stationId)
                        .plateNumber(importVO.getPlateNumber())
                        .hydrogenDate(importVO.getHydrogenDate())
                        .hydrogenQuantity(importVO.getHydrogenQuantity())
                        .unitPrice(importVO.getUnitPrice())
                        .amount(importVO.getAmount())
                        .mileage(importVO.getMileage())
                        .sourceType(SourceTypeEnum.EXCEL.getType())
                        .matchStatus(MatchStatusEnum.UNMATCHED.getStatus())
                        .uploadBatchNo(batchNo)
                        .build();
                hydrogenRecordMapper.insert(record);
                recordIds.add(record.getId());
                successCount++;
            } catch (Exception e) {
                log.warn("[importRecords] Failed to import record: {}", importVO, e);
                failCount++;
            }
        }
        // Publish event
        if (!recordIds.isEmpty()) {
            eventPublisher.publishEvent(new RecordImportedEvent(stationId, recordIds, batchNo));
        }
        Map<String, Integer> result = new HashMap<>();
        result.put("success", successCount);
        result.put("fail", failCount);
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void matchRecord(Long id, Long vehicleId, Long customerId) {
        EnergyHydrogenRecordDO record = validateRecordExists(id);
        hydrogenRecordMapper.updateById(EnergyHydrogenRecordDO.builder()
                .id(id)
                .vehicleId(vehicleId)
                .customerId(customerId)
                .matchStatus(MatchStatusEnum.MATCHED.getStatus())
                .build());
        // 手动匹配功能保留，批量匹配和明细生成由 RecordImportedEvent 统一处理
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Integer> batchMatch() {
        // Query all match-failed records (matchStatus = 2)
        List<EnergyHydrogenRecordDO> failedRecords = hydrogenRecordMapper.selectList(
                new LambdaQueryWrapperX<EnergyHydrogenRecordDO>()
                        .eq(EnergyHydrogenRecordDO::getMatchStatus, MatchStatusEnum.UNMATCHABLE.getStatus()));
        int successCount = 0;
        int failCount = 0;
        for (EnergyHydrogenRecordDO record : failedRecords) {
            try {
                // TODO: Look up vehicle by plateNumber from asset module
                // For now, mark all as still failed
                failCount++;
            } catch (Exception e) {
                failCount++;
            }
        }
        Map<String, Integer> result = new HashMap<>();
        result.put("successCount", successCount);
        result.put("failCount", failCount);
        return result;
    }

    // ==================== 三步导入接口 ====================

    @Override
    public HydrogenRecordImportPreviewVO importPreview(Long stationId, List<HydrogenRecordImportVO> list) {
        if (list == null || list.isEmpty()) {
            throw exception(HYDROGEN_RECORD_IMPORT_EMPTY);
        }

        String batchNo = generateBatchNo();

        List<HydrogenRecordImportPreviewVO.RecordPreviewItem> validRecords = new ArrayList<>();
        List<HydrogenRecordImportPreviewVO.RecordPreviewItem> duplicateRecords = new ArrayList<>();
        List<HydrogenRecordImportPreviewVO.ImportErrorItem> errors = new ArrayList<>();

        // Build a set of keys for existing DB records (stationId+plateNumber+hydrogenDate)
        // We'll check duplicates on-the-fly per record to avoid pulling too much data at once
        Set<String> seenInBatch = new HashSet<>();

        int rowNum = 1;
        for (HydrogenRecordImportVO importVO : list) {
            // Validate required fields
            if (importVO.getPlateNumber() == null || importVO.getPlateNumber().isBlank()) {
                HydrogenRecordImportPreviewVO.ImportErrorItem err = new HydrogenRecordImportPreviewVO.ImportErrorItem();
                err.setRowNum(rowNum);
                err.setReason("车牌号不能为空");
                errors.add(err);
                rowNum++;
                continue;
            }
            if (importVO.getHydrogenDate() == null) {
                HydrogenRecordImportPreviewVO.ImportErrorItem err = new HydrogenRecordImportPreviewVO.ImportErrorItem();
                err.setRowNum(rowNum);
                err.setReason("加氢日期不能为空");
                errors.add(err);
                rowNum++;
                continue;
            }
            if (importVO.getHydrogenQuantity() == null) {
                HydrogenRecordImportPreviewVO.ImportErrorItem err = new HydrogenRecordImportPreviewVO.ImportErrorItem();
                err.setRowNum(rowNum);
                err.setReason("加氢量不能为空");
                errors.add(err);
                rowNum++;
                continue;
            }

            // Check duplicate: stationId + plateNumber + hydrogenDate
            String dedupKey = stationId + ":" + importVO.getPlateNumber() + ":" + importVO.getHydrogenDate();
            boolean isDuplicateInBatch = seenInBatch.contains(dedupKey);
            boolean isDuplicateInDb = false;
            if (!isDuplicateInBatch) {
                Long count = hydrogenRecordMapper.selectCount(
                        new LambdaQueryWrapperX<EnergyHydrogenRecordDO>()
                                .eq(EnergyHydrogenRecordDO::getStationId, stationId)
                                .eq(EnergyHydrogenRecordDO::getPlateNumber, importVO.getPlateNumber())
                                .eq(EnergyHydrogenRecordDO::getHydrogenDate, importVO.getHydrogenDate()));
                isDuplicateInDb = count != null && count > 0;
            }

            HydrogenRecordImportPreviewVO.RecordPreviewItem item = new HydrogenRecordImportPreviewVO.RecordPreviewItem();
            item.setRowNum(rowNum);
            item.setPlateNumber(importVO.getPlateNumber());
            item.setHydrogenDate(importVO.getHydrogenDate());
            item.setHydrogenQuantity(importVO.getHydrogenQuantity());
            item.setUnitPrice(importVO.getUnitPrice());
            item.setAmount(importVO.getAmount());
            item.setMileage(importVO.getMileage());
            item.setIsDuplicate(isDuplicateInBatch || isDuplicateInDb);

            if (isDuplicateInBatch || isDuplicateInDb) {
                duplicateRecords.add(item);
            } else {
                validRecords.add(item);
                seenInBatch.add(dedupKey);
            }

            rowNum++;
        }

        // Build cache payload
        Map<String, Object> cachePayload = new HashMap<>();
        cachePayload.put("stationId", stationId);
        cachePayload.put("validRecords", validRecords);
        cachePayload.put("duplicateRecords", duplicateRecords);

        // Store in Redis with 15-minute TTL
        stringRedisTemplate.opsForValue().set(
                REDIS_KEY_PREVIEW + batchNo,
                JSONUtil.toJsonStr(cachePayload),
                PREVIEW_TTL_MINUTES,
                TimeUnit.MINUTES);

        // Build response
        HydrogenRecordImportPreviewVO result = new HydrogenRecordImportPreviewVO();
        result.setBatchNo(batchNo);
        result.setTotalCount(list.size());
        result.setValidCount(validRecords.size());
        result.setDuplicateCount(duplicateRecords.size());
        result.setErrorCount(errors.size());
        // Return first 50 preview records
        result.setRecords(validRecords.stream().limit(50).collect(Collectors.toList()));
        result.setDuplicates(duplicateRecords.stream().limit(50).collect(Collectors.toList()));
        result.setErrors(errors);
        return result;
    }

    @Override
    public Map<String, Integer> importConfirm(String batchNo, String duplicateStrategy) {
        // Validate duplicateStrategy
        if (!"skip".equals(duplicateStrategy) && !"overwrite".equals(duplicateStrategy)) {
            throw new IllegalArgumentException("duplicateStrategy 必须为 skip 或 overwrite");
        }
        // Read cached preview data
        String cacheJson = stringRedisTemplate.opsForValue().get(REDIS_KEY_PREVIEW + batchNo);
        if (cacheJson == null || cacheJson.isBlank()) {
            throw exception(HYDROGEN_RECORD_IMPORT_PREVIEW_EXPIRED);
        }

        cn.hutool.json.JSONObject payload = JSONUtil.parseObj(cacheJson);
        Long stationId = payload.getLong("stationId");

        List<HydrogenRecordImportPreviewVO.RecordPreviewItem> validRecords =
                JSONUtil.toList(payload.getJSONArray("validRecords"), HydrogenRecordImportPreviewVO.RecordPreviewItem.class);
        List<HydrogenRecordImportPreviewVO.RecordPreviewItem> duplicateRecords =
                JSONUtil.toList(payload.getJSONArray("duplicateRecords"), HydrogenRecordImportPreviewVO.RecordPreviewItem.class);

        // Determine records to insert based on strategy
        List<HydrogenRecordImportPreviewVO.RecordPreviewItem> toInsert = new ArrayList<>(validRecords);
        if ("overwrite".equals(duplicateStrategy)) {
            toInsert.addAll(duplicateRecords);
        }

        int total = toInsert.size();

        // Set initial progress
        HydrogenRecordImportProgressVO progress = new HydrogenRecordImportProgressVO();
        progress.setCurrent(0);
        progress.setTotal(total);
        progress.setStatus("processing");
        stringRedisTemplate.opsForValue().set(
                REDIS_KEY_PROGRESS + batchNo,
                JSONUtil.toJsonStr(progress),
                PREVIEW_TTL_MINUTES,
                TimeUnit.MINUTES);

        int successCount = 0;
        int failCount = 0;
        List<Long> recordIds = new ArrayList<>();

        for (int i = 0; i < toInsert.size(); i++) {
            HydrogenRecordImportPreviewVO.RecordPreviewItem item = toInsert.get(i);
            try {
                if ("overwrite".equals(duplicateStrategy) && Boolean.TRUE.equals(item.getIsDuplicate())) {
                    // Delete existing record(s) with same key before inserting
                    hydrogenRecordMapper.delete(
                            new LambdaQueryWrapperX<EnergyHydrogenRecordDO>()
                                    .eq(EnergyHydrogenRecordDO::getStationId, stationId)
                                    .eq(EnergyHydrogenRecordDO::getPlateNumber, item.getPlateNumber())
                                    .eq(EnergyHydrogenRecordDO::getHydrogenDate, item.getHydrogenDate()));
                }
                EnergyHydrogenRecordDO record = EnergyHydrogenRecordDO.builder()
                        .stationId(stationId)
                        .plateNumber(item.getPlateNumber())
                        .hydrogenDate(item.getHydrogenDate())
                        .hydrogenQuantity(item.getHydrogenQuantity())
                        .unitPrice(item.getUnitPrice())
                        .amount(item.getAmount())
                        .mileage(item.getMileage())
                        .sourceType(SourceTypeEnum.EXCEL.getType())
                        .matchStatus(MatchStatusEnum.UNMATCHED.getStatus())
                        .uploadBatchNo(batchNo)
                        .build();
                hydrogenRecordMapper.insert(record);
                recordIds.add(record.getId());
                successCount++;
            } catch (Exception e) {
                log.warn("[importConfirm] Failed to import record row {}: {}", item.getRowNum(), e.getMessage());
                failCount++;
            }

            // Update progress
            progress.setCurrent(i + 1);
            stringRedisTemplate.opsForValue().set(
                    REDIS_KEY_PROGRESS + batchNo,
                    JSONUtil.toJsonStr(progress),
                    PREVIEW_TTL_MINUTES,
                    TimeUnit.MINUTES);
        }

        // Mark progress as completed (partial success if some failed)
        progress.setStatus(failCount == 0 ? "completed" : (successCount > 0 ? "completed" : "failed"));
        stringRedisTemplate.opsForValue().set(
                REDIS_KEY_PROGRESS + batchNo,
                JSONUtil.toJsonStr(progress),
                PREVIEW_TTL_MINUTES,
                TimeUnit.MINUTES);

        // Publish event
        if (!recordIds.isEmpty()) {
            eventPublisher.publishEvent(new RecordImportedEvent(stationId, recordIds, batchNo));
        }

        // Clean up preview cache
        stringRedisTemplate.delete(REDIS_KEY_PREVIEW + batchNo);

        Map<String, Integer> result = new HashMap<>();
        result.put("successCount", successCount);
        result.put("failCount", failCount);
        return result;
    }

    @Override
    public HydrogenRecordImportProgressVO getImportProgress(String batchNo) {
        String json = stringRedisTemplate.opsForValue().get(REDIS_KEY_PROGRESS + batchNo);
        if (json == null || json.isBlank()) {
            HydrogenRecordImportProgressVO vo = new HydrogenRecordImportProgressVO();
            vo.setCurrent(0);
            vo.setTotal(0);
            vo.setStatus("not_found");
            return vo;
        }
        return JSONUtil.toBean(json, HydrogenRecordImportProgressVO.class);
    }

    // ==================== 私有方法 ====================

    private EnergyHydrogenRecordDO validateRecordExists(Long id) {
        EnergyHydrogenRecordDO record = hydrogenRecordMapper.selectById(id);
        if (record == null) {
            throw exception(HYDROGEN_RECORD_NOT_EXISTS);
        }
        return record;
    }

    private String generateBatchNo() {
        return "IMP" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
                + UUID.randomUUID().toString().substring(0, 4).toUpperCase();
    }
}
