package com.usky.vpp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.file.domain.FilesUpload;
import com.ruoyi.file.mapper.FilesMapper;
import com.usky.common.core.bean.CommonPage;
import com.usky.common.core.exception.BusinessException;
import com.usky.common.security.utils.SecurityUtils;
import com.usky.vpp.domain.VppFileArchive;
import com.usky.vpp.mapper.VppFileArchiveMapper;
import com.usky.vpp.service.VppArchiveService;
import com.usky.vpp.service.VppSiteService;
import com.usky.vpp.service.vo.VppFileArchiveRequestVO;
import com.usky.vpp.service.vo.VppFileArchiveResponseVO;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * VppArchiveService 默认实现（待按业务完善）
 */
@Service
public class VppArchiveServiceImpl implements VppArchiveService {

    @Autowired
    private FilesMapper filesMapper;
    @Autowired
    private VppFileArchiveMapper vppFileArchiveMapper;
    @Autowired
    private VppSiteService vppSiteService;

    /**
     * 连接池化的 RestTemplate，避免每次创建新连接
     */
    private final RestTemplate restTemplate;

    /**
     * 并行下载线程池
     */
    private static final ExecutorService DOWNLOAD_EXECUTOR = new ThreadPoolExecutor(
            4, 8, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(200),
            r -> {
                Thread t = new Thread(r, "archive-download-");
                t.setDaemon(true);
                return t;
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    {
        // 配置 HttpClient 连接池（最大连接数、超时时间）
        CloseableHttpClient httpClient = HttpClientBuilder.create()
                .setMaxConnTotal(50)
                .setMaxConnPerRoute(20)
                .build();
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
        factory.setConnectTimeout(10_000);
        factory.setReadTimeout(30_000);
        this.restTemplate = new RestTemplate(factory);
    }

    @Override
    public Boolean create(VppFileArchiveRequestVO vo) {
        String username = SecurityUtils.getUsername();
        Integer tenantId = SecurityUtils.getTenantId();

        LambdaQueryWrapper<FilesUpload> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(FilesUpload::getFilesName, vo.getName())
                .eq(FilesUpload::getUrl, vo.getUrl())
                //.eq(FilesUpload::getEnable, "1")
                .eq(FilesUpload::getIsDelete, 0);
        FilesUpload filesUpload = filesMapper.selectOne(queryWrapper);

        VppFileArchive vppFileArchive = new VppFileArchive();
        vppFileArchive.setArchiveName(vo.getArchiveName());
        vppFileArchive.setArchiveType(vo.getArchiveType());
        vppFileArchive.setFileName(filesUpload.getFilesName());
        vppFileArchive.setFileUrl(filesUpload.getUrl());
        vppFileArchive.setFileType(filesUpload.getType());
        vppFileArchive.setFileSize(filesUpload.getSize());
        vppFileArchive.setBizId(vo.getBizId() != null ? vo.getBizId() : 0L);
        vppFileArchive.setBizType(StringUtils.isNotBlank(vo.getBizType()) ? vo.getBizType() : "");
        vppFileArchive.setSiteId(vo.getSiteId());
        vppFileArchive.setCreateTime(LocalDateTime.now());
        vppFileArchive.setCreatedBy(username);
        vppFileArchive.setVersion("V1.0");
        vppFileArchive.setTenantId(tenantId);
        vppFileArchive.setRemark(StringUtils.isNotBlank(vo.getRemark()) ? vo.getRemark() : "");
        vppFileArchive.setDeleteFlag(0);
        int insert = vppFileArchiveMapper.insert(vppFileArchive);

        return insert > 0;
    }

    @Override
    public CommonPage<VppFileArchiveResponseVO> page(Long id, String archiveName, Integer archiveType, Long siteId, String bizType, Long bizId, Integer pageNum, Integer pageSize) {
        IPage<VppFileArchive> page = new Page<>(pageNum, pageSize);

        LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(VppFileArchive::getDeleteFlag, 0)
                .eq(VppFileArchive::getTenantId, SecurityUtils.getTenantId())
                .eq(id != null, VppFileArchive::getId, id)
                .eq(siteId != null, VppFileArchive::getSiteId, siteId)
                .eq(archiveType != null, VppFileArchive::getArchiveType, archiveType)
                .eq(StringUtils.isNotBlank(bizType), VppFileArchive::getBizType, bizType)
                .eq(bizId != null, VppFileArchive::getBizId, bizId)
                .like(StringUtils.isNotBlank(archiveName), VppFileArchive::getArchiveName, archiveName)
                .orderByDesc(VppFileArchive::getCreateTime);
        page = vppFileArchiveMapper.selectPage(page, queryWrapper);

        List<VppFileArchiveResponseVO> list = page.getRecords().stream().map(entity -> {
            VppFileArchiveResponseVO responseVO = new VppFileArchiveResponseVO();
            responseVO.setId(entity.getId());
            responseVO.setArchiveName(entity.getArchiveName());
            responseVO.setFileName(entity.getFileName());
            responseVO.setFileUrl(entity.getFileUrl());
            responseVO.setArchiveType(entity.getArchiveType());
            responseVO.setFileType(entity.getFileType());
            responseVO.setFileSize(convertFileSize(entity.getFileSize()));
            responseVO.setBizType(entity.getBizType());
            responseVO.setBizId(entity.getBizId());
            responseVO.setSiteId(entity.getSiteId());
            responseVO.setRemark(entity.getRemark());
            responseVO.setCreatedBy(entity.getCreatedBy());
            responseVO.setCreateTime(entity.getCreateTime());
            responseVO.setUpdateTime(entity.getUpdateTime());
            responseVO.setUpdatedBy(entity.getUpdatedBy());
            responseVO.setVersion(entity.getVersion());
            responseVO.setTenantId(entity.getTenantId());
            responseVO.setSiteName(vppSiteService.getSite(entity.getSiteId()).getSiteName());
            return responseVO;
        }).collect(java.util.stream.Collectors.toList());

        return new CommonPage<>(list, page.getTotal(), pageSize, pageNum);
    }

    @Override
    public Boolean upload(VppFileArchiveRequestVO vo) {
        if (vo.getId() == null) {
            throw new BusinessException("主键ID不能为空！");
        }

        LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(VppFileArchive::getId, vo.getId())
                .eq(VppFileArchive::getTenantId, SecurityUtils.getTenantId())
                .eq(VppFileArchive::getDeleteFlag, 0);
        VppFileArchive existingRecord = vppFileArchiveMapper.selectOne(queryWrapper);

        if (existingRecord == null) {
            throw new BusinessException("档案记录不存在或已被删除！");
        }

        // 根据 name + url 关联 FilesUpload 表，同步更新文件信息
        if (StringUtils.isNotBlank(vo.getName()) && StringUtils.isNotBlank(vo.getUrl())) {
            LambdaQueryWrapper<FilesUpload> fileQueryWrapper = new LambdaQueryWrapper<>();
            fileQueryWrapper.eq(FilesUpload::getFilesName, vo.getName())
                    .eq(FilesUpload::getUrl, vo.getUrl())
                    .eq(FilesUpload::getIsDelete, 0);
            FilesUpload filesUpload = filesMapper.selectOne(fileQueryWrapper);

            if (filesUpload != null) {
                existingRecord.setFileName(filesUpload.getFilesName());
                existingRecord.setFileUrl(filesUpload.getUrl());
                existingRecord.setFileType(filesUpload.getType());
                existingRecord.setFileSize(filesUpload.getSize());
            } else {
                throw new BusinessException("未找到匹配的文件记录，请确认文件名与路径是否正确！");
            }
        }

        // 仅更新非空字段
        if (StringUtils.isNotBlank(vo.getArchiveName())) {
            existingRecord.setArchiveName(vo.getArchiveName());
        }
        if (vo.getArchiveType() != null) {
            existingRecord.setArchiveType(vo.getArchiveType());
        }
        if (StringUtils.isNotBlank(vo.getRemark())) {
            existingRecord.setRemark(vo.getRemark());
        }
        if (vo.getSiteId() != null) {
            existingRecord.setSiteId(vo.getSiteId());
        }

        // 版本号自增 +0.1
        String currentVersion = existingRecord.getVersion();
        if (StringUtils.isNotBlank(currentVersion) && currentVersion.startsWith("V")) {
            try {
                double versionNum = Double.parseDouble(currentVersion.substring(1));
                versionNum += 0.1;
                existingRecord.setVersion("V" + String.format("%.1f", versionNum));
            } catch (NumberFormatException e) {
                existingRecord.setVersion(currentVersion + ".1");
            }
        }

        existingRecord.setUpdateTime(LocalDateTime.now());
        existingRecord.setUpdatedBy(SecurityUtils.getUsername());

        int update = vppFileArchiveMapper.updateById(existingRecord);
        return update > 0;
    }

    @Override
    public Boolean delete(Long id) {
        LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(VppFileArchive::getId, id)
                .eq(VppFileArchive::getTenantId, SecurityUtils.getTenantId());
        VppFileArchive vppFileArchive = vppFileArchiveMapper.selectOne(queryWrapper);
        vppFileArchive.setDeleteFlag(1);
        vppFileArchive.setDeletedAt(LocalDateTime.now());
        vppFileArchive.setUpdatedBy(SecurityUtils.getUsername());
        int i = vppFileArchiveMapper.updateById(vppFileArchive);
        return i > 0;
    }

    @Override
    public void export(List<Long> ids, HttpServletResponse response) {
        // ========== 参数校验 ==========
        if (ids == null || ids.isEmpty()) {
            writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "请选择要导出的档案！");
            return;
        }

        // ========== 查询档案记录 ==========
        LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(VppFileArchive::getId, ids)
                .eq(VppFileArchive::getDeleteFlag, 0)
                .eq(VppFileArchive::getTenantId, SecurityUtils.getTenantId());
        List<VppFileArchive> archives = vppFileArchiveMapper.selectList(queryWrapper);

        if (archives == null || archives.isEmpty()) {
            writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "没有找到有效的档案记录！");
            return;
        }

        // ========== 预解析文件名（去重处理，必须在并行下载之前完成）==========
        List<DownloadTask> tasks = new ArrayList<>();
        Set<String> usedNames = new HashSet<>();
        for (VppFileArchive archive : archives) {
            String fileUrl = archive.getFileUrl();
            String fileName = archive.getFileName();
            if (StringUtils.isBlank(fileUrl) || StringUtils.isBlank(fileName)) {
                continue;
            }
            String zipEntryName = resolveDuplicateName(fileName, usedNames);
            usedNames.add(zipEntryName);
            tasks.add(new DownloadTask(zipEntryName, fileUrl));
        }

        if (tasks.isEmpty()) {
            writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "没有可下载的文件！");
            return;
        }

        // ========== 构造 ZIP 文件名 ==========
        String zipFileName = "电子档案_"
                + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
                + ".zip";

        // ========== 设置响应头（必须在写入流之前）==========
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=" + encodeFileName(zipFileName));

        // ========== 并行下载所有文件 ==========
        List<CompletableFuture<DownloadTask>> futures = tasks.stream()
                .map(task -> CompletableFuture.supplyAsync(() -> {
                    try {
                        byte[] bytes = restTemplate.getForObject(task.fileUrl, byte[].class);
                        task.fileBytes = bytes;
                    } catch (Exception e) {
                        task.error = e.getMessage();
                    }
                    return task;
                }, DOWNLOAD_EXECUTOR))
                .collect(Collectors.toList());

        // ========== 生成 ZIP 并写入响应流 ==========
        try (ServletOutputStream sos = response.getOutputStream();
             BufferedOutputStream bos = new BufferedOutputStream(sos, 8192);
             ZipOutputStream zos = new ZipOutputStream(bos)) {

            for (CompletableFuture<DownloadTask> future : futures) {
                DownloadTask task = future.join(); // 阻塞等待当前文件下载完成
                try {
                    if (task.error != null) {
                        // 下载失败，写入错误信息
                        ZipEntry errorEntry = new ZipEntry(task.zipEntryName + ".error.txt");
                        zos.putNextEntry(errorEntry);
                        zos.write(("下载失败: " + task.error).getBytes(StandardCharsets.UTF_8));
                        zos.closeEntry();
                    } else if (task.fileBytes != null && task.fileBytes.length > 0) {
                        ZipEntry zipEntry = new ZipEntry(task.zipEntryName);
                        zos.putNextEntry(zipEntry);
                        zos.write(task.fileBytes);
                        zos.closeEntry();
                    }
                } catch (Exception e) {
                    ZipEntry errorEntry = new ZipEntry(task.zipEntryName + ".error.txt");
                    zos.putNextEntry(errorEntry);
                    zos.write(("写入ZIP失败: " + e.getMessage()).getBytes(StandardCharsets.UTF_8));
                    zos.closeEntry();
                }
            }
            zos.finish();

        } catch (Exception e) {
            // 如果响应头还没提交，可以返回错误信息
            if (!response.isCommitted()) {
                writeErrorResponse(response, HttpStatus.INTERNAL_SERVER_ERROR.value(),
                        "导出电子档案失败: " + e.getMessage());
            }
        }
    }

    /**
     * 下载任务内部类，承载文件名、URL、下载结果
     */
    private static class DownloadTask {
        final String zipEntryName;
        final String fileUrl;
        volatile byte[] fileBytes;
        volatile String error;

        DownloadTask(String zipEntryName, String fileUrl) {
            this.zipEntryName = zipEntryName;
            this.fileUrl = fileUrl;
        }
    }

    /**
     * 处理 ZIP 内重名文件，自动添加编号后缀
     * @param fileName  原始文件名
     * @param usedNames 已使用的文件名集合
     * @return 去重后的文件名
     */
    private String resolveDuplicateName(String fileName, Set<String> usedNames) {
        if (!usedNames.contains(fileName)) {
            return fileName;
        }
        String baseName = fileName;
        String extension = "";
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex > 0) {
            baseName = fileName.substring(0, dotIndex);
            extension = fileName.substring(dotIndex);
        }
        int counter = 1;
        String newName;
        do {
            newName = baseName + "(" + counter + ")" + extension;
            counter++;
        } while (usedNames.contains(newName));
        return newName;
    }

    /**
     * 向响应中写入 JSON 错误信息
     */
    private void writeErrorResponse(HttpServletResponse response, int status, String message) {
        response.setStatus(status);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        try {
            response.getWriter().write("{\"code\":\"" + status + "\",\"msg\":\"" + message + "\"}");
        } catch (IOException e) {
            // 忽略写入异常
        }
    }

    /**
     * 编码文件名，兼容各浏览器
     */
    private String encodeFileName(String fileName) {
        try {
            return URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            return fileName;
        }
    }

    /**
     * 文件大小格式转换工具方法
     * 按1024进制自动递进换算单位（KB → MB → GB → TB）
     * @param size 数值型文件大小
     * @return 带单位后缀的格式化字符串
     */
    private String convertFileSize(Double size) {
        if (size == null || size <= 0) {
            return "0 B";
        }
        final String[] units = {"B", "KB", "MB", "GB", "TB"};
        int unitIndex = 0;
        double result = size;
        while (result >= 1024 && unitIndex < units.length - 1) {
            result /= 1024;
            unitIndex++;
        }
        return String.format("%.2f %s", result, units[unitIndex]);
    }
}
