package com.weather.modules.weather.filescan.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.weather.common.constant.Constant;
import com.weather.common.page.PageData;
import com.weather.common.service.impl.CrudServiceImpl;
import com.weather.common.utils.ConvertUtils;
import com.weather.modules.security.user.SecurityUser;
import com.weather.modules.sys.dao.SysDeptDao;
import com.weather.modules.sys.dto.SysDeptDTO;
import com.weather.modules.sys.entity.SysDeptEntity;
import com.weather.modules.sys.service.SysDeptService;
import com.weather.modules.weather.filescan.dao.WeatherFileScanRecordDao;
import com.weather.modules.weather.filescan.dto.DeptFileGroupVO;
import com.weather.modules.weather.filescan.dto.DeptFileItemVO;
import com.weather.modules.weather.filescan.dto.ModelFileGroupVO;
import com.weather.modules.weather.filescan.dto.ModelFileItemVO;
import com.weather.modules.weather.filescan.dto.WeatherFileScanRecordDTO;
import com.weather.modules.weather.filescan.entity.WeatherFileScanRecordEntity;
import com.weather.modules.weather.filescan.service.WeatherFileScanRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class WeatherFileScanRecordServiceImpl extends CrudServiceImpl<WeatherFileScanRecordDao, WeatherFileScanRecordEntity, WeatherFileScanRecordDTO> implements WeatherFileScanRecordService {

    private final SysDeptService sysDeptService;
    private final SysDeptDao sysDeptDao;

    /** 部门名称缓存，部门数据变化频率极低，缓存5分钟 */
    private volatile Map<Long, String> deptNameCache;
    private volatile long deptNameCacheExpireAt;

    @Override
    public PageData<WeatherFileScanRecordDTO> page(Map<String, Object> params) {
        IPage<WeatherFileScanRecordEntity> page = baseDao.selectPage(
                getPage(params, null, false),
                getWrapper(params)
        );
        PageData<WeatherFileScanRecordDTO> pageData = getPageData(page, WeatherFileScanRecordDTO.class);
        enrichDeptNames(pageData.getList());
        return pageData;
    }

    private Map<Long, String> getDeptNameMap() {
        long now = System.currentTimeMillis();
        if (deptNameCache != null && now < deptNameCacheExpireAt) {
            return deptNameCache;
        }
        synchronized (this) {
            if (deptNameCache != null && now < deptNameCacheExpireAt) {
                return deptNameCache;
            }
            deptNameCache = sysDeptService.list(Map.of()).stream()
                    .collect(Collectors.toMap(SysDeptDTO::getId, SysDeptDTO::getName, (a, b) -> a));
            deptNameCacheExpireAt = now + 5 * 60 * 1000;
            return deptNameCache;
        }
    }

    private void enrichDeptNames(List<WeatherFileScanRecordDTO> list) {
        Map<Long, String> deptMap = getDeptNameMap();
        for (WeatherFileScanRecordDTO dto : list) {
            if (dto.getDeptId() != null) {
                dto.setDeptName(deptMap.get(dto.getDeptId()));
            }
        }
    }

    @Override
    public QueryWrapper<WeatherFileScanRecordEntity> getWrapper(Map<String, Object> params) {
        QueryWrapper<WeatherFileScanRecordEntity> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc(Constant.CREATE_DATE);
        return wrapper;
    }

    /** category → 展示顺序，数字越小越靠前 */
    private static final Map<String, Integer> CATEGORY_ORDER = Map.of(
            "日平均气温", 1,
            "日平均降雨量", 3,
            "月平均气温图", 5,
            "月降雨量图", 6,
            "累计降雨量", 7,
            "631信息", 9
    );

    @Override
    public List<DeptFileGroupVO> getDeptFileTree(Long deptId) {
        List<WeatherFileScanRecordEntity> records = baseDao.selectList(
                new QueryWrapper<WeatherFileScanRecordEntity>()
                        .eq("status", Constant.FileScanStatus.DISPLAYED.getValue())
                        .eq("dept_id", deptId)
        );

        SysDeptEntity dept = sysDeptDao.selectById(deptId);
        String deptName = dept != null ? dept.getName() : String.valueOf(deptId);

        List<DeptFileItemVO> fileList = records.stream()
                .sorted((a, b) -> {
                    int orderA = CATEGORY_ORDER.getOrDefault(a.getKeywordCategory(), Integer.MAX_VALUE);
                    int orderB = CATEGORY_ORDER.getOrDefault(b.getKeywordCategory(), Integer.MAX_VALUE);
                    if (orderA != orderB) return Integer.compare(orderA, orderB);
                    // 同 category 内：非全年排在全年前面
                    return Comparator.nullsFirst(String::compareTo)
                            .compare(a.getKeywordPeriod(), b.getKeywordPeriod());
                })
                .map(r -> {
                    DeptFileItemVO item = new DeptFileItemVO();
                    item.setFileId(r.getId());
                    item.setFileName(r.getFileName());
                    item.setDisplayName(stripExtension(r.getFileName()));
                    item.setType(r.getFileExt());
                    if ("txt".equalsIgnoreCase(r.getFileExt())) {
                        item.setContent(readTextContent(r.getDisplayPath()));
                    }
                    return item;
                })
                .collect(Collectors.toList());

        DeptFileGroupVO group = new DeptFileGroupVO();
        group.setDeptId(deptId);
        group.setDeptName(deptName);
        group.setFileList(fileList);

        return List.of(group);
    }

    /** 去掉文件名后缀，如 "临武县-日平均气温.png" → "临武县-日平均气温" */
    private String stripExtension(String fileName) {
        if (StrUtil.isBlank(fileName)) return fileName;
        int dotIdx = fileName.lastIndexOf('.');
        return dotIdx > 0 ? fileName.substring(0, dotIdx) : fileName;
    }

    private String readTextContent(String displayPath) {
        if (StrUtil.isBlank(displayPath)) return null;
        try {
            return Files.readString(Path.of(displayPath));
        } catch (IOException e) {
            log.warn("读取 txt 文件内容失败: {}", displayPath, e);
            return null;
        }
    }

    @Override
    public ModelFileGroupVO getModelFiles() {
        List<WeatherFileScanRecordEntity> records = baseDao.selectList(
                new QueryWrapper<WeatherFileScanRecordEntity>()
                        .isNull("dept_id")
                        .eq("keyword_category", "模式预报")
                        .eq("status", Constant.FileScanStatus.DISPLAYED.getValue())
                        .orderByAsc("keyword_region")
        );

        ModelFileGroupVO group = new ModelFileGroupVO();
        group.setType("模式预报");

        List<ModelFileItemVO> fileList = records.stream()
                .map(r -> {
                    ModelFileItemVO item = new ModelFileItemVO();
                    item.setFileName(r.getFileName());
                    // 已映射的用模型名，未映射的退回文件名（不带后缀）
                    item.setDisplayName(StrUtil.isNotBlank(r.getKeywordRegion())
                            ? r.getKeywordRegion()
                            : stripExtension(r.getFileName()));
                    item.setFileId(r.getId());
                    item.setType(r.getFileExt());
                    return item;
                })
                .collect(Collectors.toList());
        group.setFileList(fileList);
        return group;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void archive(Long id) {
        WeatherFileScanRecordEntity entity = baseDao.selectById(id);
        if (entity == null || entity.getStatus() == Constant.FileScanStatus.ARCHIVED.getValue()) {
            return;
        }

        if (StrUtil.isNotBlank(entity.getDisplayPath())) {
            Path displayPath = Paths.get(entity.getDisplayPath());
            String archiveDir = entity.getDisplayPath()
                    .replace("\\display\\", "\\archive\\")
                    .replace("/display/", "/archive/");
            Path archivePath = Paths.get(archiveDir);
            try {
                Files.createDirectories(archivePath.getParent());
                Files.move(displayPath, archivePath, StandardCopyOption.REPLACE_EXISTING);
                entity.setArchivePath(archivePath.toString());
            } catch (IOException e) {
                throw new RuntimeException("归档文件移动失败: " + entity.getFileName(), e);
            }
        }

        entity.setStatus(Constant.FileScanStatus.ARCHIVED.getValue());
        entity.setArchivedAt(new Date());
        entity.setUpdater(SecurityUser.getUser().getId());
        entity.setUpdateDate(new Date());
        updateById(entity);
    }
}
