package com.multictrl.modules.business.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.multictrl.common.config.MinioConfig;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.exception.RenException;
import com.multictrl.common.page.PageData;
import com.multictrl.common.service.impl.CrudServiceImpl;
import cn.hutool.core.util.StrUtil;
import com.multictrl.common.utils.ConvertUtils;
import com.multictrl.common.utils.DateUtils;
import com.multictrl.modules.business.dao.MediaFileDao;
import com.multictrl.modules.business.dto.MediaFileDTO;
import com.multictrl.modules.business.entity.MediaFileEntity;
import com.multictrl.modules.business.service.MediaFileService;
import com.multictrl.modules.security.user.SecurityUser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 媒体资源
 *
 * @author Sdy
 * @since 1.0.0 2026-04-29
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MediaFileServiceImpl extends CrudServiceImpl<MediaFileDao, MediaFileEntity, MediaFileDTO> implements MediaFileService {
    private final MinioConfig minioConfig;

    @Override
    public QueryWrapper<MediaFileEntity> getWrapper(Map<String, Object> params) {
        String id = (String) params.get("id");
        String taskId = (String) params.get("taskId");

        QueryWrapper<MediaFileEntity> wrapper = new QueryWrapper<>();
        wrapper.eq(StrUtil.isNotBlank(id), "id", id);
        wrapper.eq(StrUtil.isNotBlank(taskId), "task_id", taskId);
        wrapper.orderByDesc("created_time");

        return wrapper;
    }

    @Override
    public PageData<JSONObject> pageList(Map<String, Object> params) {
        IPage<MediaFileEntity> iPage = baseDao.pageList(getPage(params, null, false), getWrapperBy(params));
        PageData<MediaFileDTO> page = getPageData(iPage, MediaFileDTO.class);
        List<JSONObject> newList = new ArrayList<>();
        List<MediaFileDTO> list = page.getList();
        if (CollectionUtil.isNotEmpty(list)) {
            Map<String, List<MediaFileDTO>> collect = list.stream()
                    .filter(v -> v != null && v.getCreatedTime() != null)
                    .peek(v -> {
                        String objectKey = v.getObjectKey();
                        String subFileType = v.getSubFileType();
                        if ("video".equals(subFileType)) {
                            v.setUrl(BusinessConstant.VIDEO_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + objectKey);
                            v.setCover(BusinessConstant.IMAGE_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" +
                                    objectKey.replaceAll(".mp4", BusinessConstant.VIDEO_COVER_SUFFIX));
                        } else {
                            v.setUrl(BusinessConstant.IMAGE_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + objectKey);
                        }
                    }).collect(Collectors.groupingBy(
                            v -> DateUtils.format(v.getCreatedTime(), "yyyy年MM月dd日"),
                            () -> new TreeMap<String, List<MediaFileDTO>>(Comparator.reverseOrder()),
                            Collectors.toList()
                    ));
            for (String key : collect.keySet()) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.set("date", key);
                jsonObject.set("list", collect.get(key));
                newList.add(jsonObject);
            }
        }
        return new PageData<>(newList, page.getTotal());
    }

    @Override
    public PageData<MediaFileDTO> getAlbum(Map<String, Object> params) {
        String dockSn = (String) params.get("dockSn");
        IPage<MediaFileEntity> pageList = this.baseDao.getAlbum(getPage(params, null, false), SecurityUser.getDeptId(), dockSn);
        PageData<MediaFileDTO> pageData = getPageData(pageList, MediaFileDTO.class);
        for (MediaFileDTO dto : pageData.getList()) {
            String objectKey = dto.getObjectKey();
            dto.setUrl(BusinessConstant.IMAGE_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + objectKey);
        }
        return pageData;
    }

    @Override
    public void deleteMediaFile(Long[] ids) {
        for (Long id : ids) {
            MediaFileDTO dto = get(id);
            if (dto != null) {
                String objectKey = dto.getObjectKey();
                FileUtil.del(minioConfig.getOther().getDataPath() + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + objectKey);
                deleteById(id);
            }
        }
    }

    @Override
    public ResponseEntity<Resource> downloadMedia(Long id) {
        try {
            MediaFileDTO dto = get(id);
            // 构建文件路径（建议用 Paths.get 更安全）
            Path filePath = Paths.get(minioConfig.getOther().getDataPath(), BusinessConstant.DOCK_MEDIA_BUCKET, dto.getObjectKey());
            File file = filePath.toFile();
            if (!file.exists()) {
                return ResponseEntity.notFound().build();
            }

            String fileName = dto.getName();
            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
                    .replace("+", "%20");
            String contentDisposition = "attachment; filename*=UTF-8''" + encodedFileName;

            return ResponseEntity.ok()
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
                    .contentLength(Files.size(filePath))  // 直接传 long，安全
                    .body(new FileSystemResource(file));  // 自动管理流

        } catch (IOException e) {
            log.error("媒体文件下载失败：{}", ExceptionUtils.getStackTrace(e));
            throw new RenException(ErrorCode.INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    public ResponseEntity<StreamingResponseBody> downloadMediaByZip(Map<String, Object> params) {
        List<MediaFileEntity> list = baseDao.list(getWrapperBy(params));
        if (list.isEmpty()) {
            return ResponseEntity.notFound().build();
        }

        String zipFileName = "media_" + System.currentTimeMillis() + ".zip";
        String encodedFileName = URLEncoder.encode(zipFileName, StandardCharsets.UTF_8)
                .replace("+", "%20");
        // 使用 RFC 5987 标准编码避免乱码
        String contentDisposition = "attachment; filename*=UTF-8''" + encodedFileName;

        StreamingResponseBody streamingBody = outputStream -> {
            // 使用 try-with-resources 确保 zip 流关闭
            try (ZipOutputStream zipOut = new ZipOutputStream(outputStream)) {
                for (MediaFileEntity entity : list) {
                    try {
                        Path filePath = Paths.get(
                                minioConfig.getOther().getDataPath(),
                                BusinessConstant.DOCK_MEDIA_BUCKET,
                                entity.getObjectKey()
                        );
                        // 避免路径遍历攻击：检查最终路径是否在基础目录内（可选但推荐）
                        File file = filePath.toFile();
                        if (!file.exists()) {
                            log.warn("文件不存在，跳过: {}", filePath);
                            continue;
                        }
                        String fileName = entity.getName();
                        ZipEntry zipEntry = new ZipEntry(fileName);
                        zipOut.putNextEntry(zipEntry);
                        Files.copy(filePath, zipOut);
                        zipOut.closeEntry();
                    } catch (Exception e) {
                        // 记录错误但继续打包其他文件
                        log.error("添加文件到ZIP失败，ID: {}", entity.getId(), e);
                    }
                }
            } catch (IOException e) {
                log.error("ZIP打包过程中发生IO异常", e);
                throw new RuntimeException(e);
            }
        };

        return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType("application/zip"))
                .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
                .body(streamingBody);
    }

    @Override
    public Map<String, List<String>> oneClickGetDownloadPath(Map<String, Object> params) {
        Map<String, List<String>> map = new HashMap<>();
        List<String> images = new ArrayList<>();
        List<String> videos = new ArrayList<>();
        List<MediaFileEntity> entityList = baseDao.list(getWrapperBy(params));
        List<MediaFileDTO> dtos = ConvertUtils.sourceToTarget(entityList, MediaFileDTO.class);
        for (MediaFileDTO dto : dtos) {
            String name = dto.getName();
            if (StrUtil.containsAnyIgnoreCase(name, "mp4")) {
                String url = BusinessConstant.VIDEO_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + dto.getObjectKey();
                videos.add(url);
            } else {
                String url = BusinessConstant.IMAGE_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + dto.getObjectKey();
                images.add(url);
            }
        }
        map.put("images", images);
        map.put("videos", videos);
        return map;
    }

    @Override
    public void conditionDeleteMedia(Map<String, Object> params) {
        List<MediaFileEntity> list = baseDao.list(getWrapperBy(params));
        if (CollectionUtil.isEmpty(list)) {
            return;
        }
        for (MediaFileEntity entity : list) {
            String path = minioConfig.getOther().getDataPath() + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + entity.getObjectKey();
            FileUtil.del(path);
            deleteById(entity.getId());
        }
    }

    //获取条件
    private QueryWrapper<MediaFileEntity> getWrapperBy(Map<String, Object> params) {
        String dockSn = (String) params.get("dockSn");
        String routeId = (String) params.get("routeId");
        String startTime = (String) params.get("startTime");
        String endTime = (String) params.get("endTime");
        QueryWrapper<MediaFileEntity> wrapper = new QueryWrapper<>();
        wrapper.eq(StrUtil.isNotBlank(dockSn), "t.dock_sn", dockSn);
        if (StrUtil.isNotBlank(routeId)) {
            wrapper.eq("t.route_id", Long.parseLong(routeId));
        }
        if (StrUtil.isNotBlank(startTime) && StrUtil.isNotBlank(endTime)) {
            wrapper.between("m.created_time", DateUtils.parse(startTime, DateUtils.DATE_TIME_PATTERN), DateUtils.parse(endTime, DateUtils.DATE_TIME_PATTERN));
        }
        wrapper.orderByDesc("m.created_time"/*, "t.create_date"*/);
//        wrapper.orderByAsc("m.off_index");

        return wrapper;
    }

    @Override
    public MediaFileDao getDao() {
        return baseDao;
    }
}