
package com.yami.shop.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.JsonObject;
import com.yami.shop.bean.enums.*;
import com.yami.shop.bean.model.*;
import com.yami.shop.bean.param.*;
import com.yami.shop.bean.vo.ExportContext;
import com.yami.shop.bean.vo.ExportTaskVo;
import com.yami.shop.common.exception.GlobalException;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.common.util.R;
import com.yami.shop.dao.*;
import com.yami.shop.service.ExportTaskService;
import com.yami.shop.utils.ExportUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;

/**
 * @author lgh on 2018/09/15.
 */
@Slf4j
@Service
@AllArgsConstructor
public class ExportTaskServiceImpl extends ServiceImpl<ExportTaskMapper, ExportTask> implements ExportTaskService {

    @Autowired
    ExportTaskMapper exportTaskMapper;
    @Autowired
    private TransactionTemplate transactionTemplate;
    // 下载信号量，控制并发
    private static final Semaphore downloadSemaphore = new Semaphore(3);


    // 存储正在执行的任务
    private final Map<String, ExportContext> exportTasks = new ConcurrentHashMap<>();
    @Override
    public ExportTask findByUserIdAndStatusAndType(Long userId, int exportStatus, int exportType) {
        return baseMapper.findByUserIdAndStatusAndType(userId,exportStatus,exportType);
    }
    @Override
    public void downloadFile(HttpServletRequest request, HttpServletResponse response, String fileId, Long userId)  throws IOException  {

        ExportTask exportTask = baseMapper.findByUserIdAndId(fileId,userId);

        if (exportTask==null){
            throw new GlobalException("导出任务不存在或不属于该用户,文件id:"+fileId);
        }
        if (!(exportTask.getExportStatus()==1||exportTask.getExportStatus()==3)){
            throw new GlobalException("导出任务未生成不允许下载");
        }
        String clientIp = getClientIp(request);
        String filePath = null;
        try {
            // 2. 参数验证
            filePath = exportTask.getFileUrl();
            // 3. 路径安全验证
            String safeFilePath = ExportUtils.sanitize(exportTask.getFileUrl());
            Path safePath = Paths.get(safeFilePath).normalize();
            // 4. 文件存在性检查
            File file = safePath.toFile();
            if (!file.exists()) {
                throw new FileNotFoundException("文件不存在: " + filePath);
            }
            if (!file.isFile()) {
                throw new IllegalArgumentException("不是有效的文件: " + filePath);
            }
            // 7. 设置响应头
            setResponseHeaders(response, file, request, exportTask);
            // 8. 执行文件下载
            transferFile(file, response);
            exportTask.setExportMsg("已下载");
            exportTask.setExportStatus(3);
            exportTaskMapper.updateById(exportTask);
            log.info("文件下载成功-文件: {},IP: {}, 时间: {}ms",
                    file.getName(),
                    clientIp,
                    System.currentTimeMillis());
        }   catch (FileNotFoundException e) {
            handleException(e, e.getMessage(), response,
                    HttpStatus.NOT_FOUND.value());
        } catch (IllegalArgumentException e) {
            handleException(e, e.getMessage(), response,
                    HttpStatus.BAD_REQUEST.value());
        } catch (Exception e) {
            log.error("文件下载异常: {}", filePath, e);
            handleException(e, "服务器内部错误", response,
                    HttpStatus.INTERNAL_SERVER_ERROR.value());
        } finally {
            downloadSemaphore.release();
        }
    }

    @Override
    public void putTaskId(String taskId, ExportContext context) {
        exportTasks.put(taskId, context);
    }

    @Override
    public void removeTaskId(String taskId) {
        exportTasks.remove(taskId);
    }

    @Override
    public R<String> cancelExport(String fileId, Long userId) {
        log.info("");
        // 1. 使用事务确保数据一致性
        return transactionTemplate.execute(status -> {
            try {
                ExportTask exportTask = exportTaskMapper.findByUserIdAndId(fileId, userId);  // 加锁
                if (exportTask == null) {
                    return R.FAIL("中断失败-未查询到任务");
                }

                // 检查任务是否已结束
                if (exportTask.getExportStatus()!=0) {
                    return R.FAIL("任务不在进行中，无法取消");
                }

                // 3. 安全取消执行
                ExportContext context = exportTasks.get(exportTask.getTaskId());
                if (context != null) {
                    return safeCancelExportTask(context, exportTask);
                }

                // 上下文已不存在，直接返回成功
                return R.SUCCESS("任务已不存在，标记为取消");

            } catch (Exception e) {
                status.setRollbackOnly();
                log.error("取消导出任务失败, fileId: {}", fileId, e);
                return R.FAIL("系统错误，取消失败");
            }
        });
    }

    @Override
    public IPage<ExportTaskVo> findPage(PageParam<ExportTaskVo> page, Long userId,Integer exportType) {
        return exportTaskMapper.findPage(page,userId,exportType);
    }

    /**
     * 传输文件到响应流
     */
    private long transferFile(File file, HttpServletResponse response) throws IOException {

        long bytesTransferred = 0;
        long fileSize = file.length();

        try (RandomAccessFile raf = new RandomAccessFile(file, "r");
             FileChannel fileChannel = raf.getChannel();
             OutputStream outputStream = response.getOutputStream()) {

            WritableByteChannel outputChannel = Channels.newChannel(outputStream);

            // 使用直接缓冲区提高性能
            ByteBuffer buffer = ByteBuffer.allocateDirect(64 * 1024); // 64KB
            while (bytesTransferred < fileSize) {
                buffer.clear();
                int bytesRead = fileChannel.read(buffer);

                if (bytesRead == -1) {
                    break;
                }

                buffer.flip();
                while (buffer.hasRemaining()) {
                    outputChannel.write(buffer);
                }
                bytesTransferred += bytesRead;
            }

            outputStream.flush();

        } catch (ClosedChannelException e) {
            log.info("客户端中断了下载: {}", file.getName());
            throw e;
        }
        return bytesTransferred;
    }
    /**
     * 设置响应头
     */
    private void setResponseHeaders(HttpServletResponse response,
                                    File file,
                                    HttpServletRequest request,
                                    ExportTask exportTask) {

        // 1. 文件名处理
        String fileName = exportTask.getFileName() != null
                ? exportTask.getFileName()
                : file.getName();

        String safeFileName = sanitizeFileName(fileName);
        String encodedFileName = encodeFileNameForBrowser(safeFileName, request);

        // 2. 内容类型
        String contentType = ExportUtils.getContentType(file);
        response.setContentType(contentType);

        // 3. 下载头
        response.setHeader("Content-Disposition",
                "attachment; filename=\"" + encodedFileName + "\"");

        // 4. 文件信息
        response.setContentLengthLong(file.length());
        response.setHeader("Accept-Ranges", "none"); // 明确不支持断点续传
        response.setHeader("X-File-Size", String.valueOf(file.length()));
        response.setHeader("X-File-Name", safeFileName);
        response.setHeader("X-File-Modified",
                Instant.ofEpochMilli(file.lastModified()).toString());

        // 5. 安全头
        response.setHeader("X-Content-Type-Options", "nosniff");
        response.setHeader("X-Frame-Options", "DENY");
        response.setHeader("X-XSS-Protection", "1; mode=block");
        response.setHeader("Content-Security-Policy", "default-src 'none'");
        response.setHeader("Referrer-Policy", "no-referrer");

        // 6. 缓存控制
        response.setHeader("Cache-Control",
                "no-store, no-cache, must-revalidate, max-age=0");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires", "0");
    }

    /**
     * 文件名编码
     */
    private String encodeFileNameForBrowser(String fileName, HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");

        try {
            if (userAgent == null) {
                return URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
                        .replace("+", "%20");
            }

            if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
                // IE浏览器
                return URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
            } else if (userAgent.contains("Firefox")) {
                // Firefox
                return "=?UTF-8?B?" + Base64.getEncoder()
                        .encodeToString(fileName.getBytes(StandardCharsets.UTF_8)) + "?=";
            } else {
                // Chrome, Safari, Edge等
                return new String(fileName.getBytes(StandardCharsets.UTF_8),
                        StandardCharsets.ISO_8859_1);
            }
        } catch (Exception e) {
            log.warn("文件名编码异常，使用原始文件名: {}", fileName, e);
            return fileName;
        }
    }
    /**
     * 处理异常
     */
    private void handleException(Exception e, String message,
                                 HttpServletResponse response, int status) {
        response.setStatus(status);
        response.setContentType("application/json");

        try {
            JsonObject error = new JsonObject();
            error.addProperty("code", status);
            error.addProperty("message", message);
            error.addProperty("timestamp", Instant.now().toString());

            if (status >= 500) {
                error.addProperty("errorId", UUID.randomUUID().toString());
            }

            response.getWriter().write(error.toString());
        } catch (IOException ex) {
            log.error("写入错误响应失败", ex);
        }
    }
    /**
     * 清理文件名
     */
    private String sanitizeFileName(String fileName) {
        if (StringUtils.isBlank(fileName)) {
            return "download_" + System.currentTimeMillis();
        }

        // 移除路径分隔符和危险字符
        String sanitized = fileName
                .replaceAll("[\\\\/:*?\"<>|]", "_")
                .replaceAll("\\s+", " ")
                .trim();

        // 限制文件名长度
        int maxLength = 255;
        if (sanitized.length() > maxLength) {
            int lastDot = sanitized.lastIndexOf('.');
            if (lastDot > 0 && lastDot < maxLength - 10) {
                // 保留扩展名
                String ext = sanitized.substring(lastDot);
                String name = sanitized.substring(0, maxLength - ext.length() - 1);
                sanitized = name + "_" + ext;
            } else {
                sanitized = sanitized.substring(0, maxLength);
            }
        }

        return sanitized;
    }
    /**
     * 获取客户端IP
     */
    private String getClientIp(HttpServletRequest request) {
        String[] headers = {
                "X-Forwarded-For",
                "Proxy-Client-IP",
                "WL-Proxy-Client-IP",
                "HTTP_CLIENT_IP",
                "HTTP_X_FORWARDED_FOR"
        };

        for (String header : headers) {
            String ip = request.getHeader(header);
            if (StringUtils.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
                return ip.split(",")[0].trim();
            }
        }

        return request.getRemoteAddr();
    }


    /**
     * 安全取消导出任务
     */
    private R<String> safeCancelExportTask(ExportContext context, ExportTask exportTask) {
        // 方案2: 设置取消标志，等待任务自己结束
        context.setCancelled(true);
        return R.SUCCESS("任务正在结束，请稍后查看");
    }
}
