package cn.edu.whu.boot.allocation.controllers;


import cn.edu.whu.boot.allocation.controllers.dtos.ImageWithUrl;
import cn.edu.whu.boot.allocation.controllers.dtos.MapServerInitialExtent;
import cn.edu.whu.boot.allocation.controllers.dtos.MapServerRoot;
import cn.edu.whu.boot.allocation.entities.Image;
import cn.edu.whu.boot.allocation.services.ImageService;
import cn.edu.whu.boot.allocation.services.TaskCellService;
import cn.edu.whu.boot.allocation.services.TaskInfoService;
import cn.edu.whu.boot.common.domain.ResultMessage;
import cn.edu.whu.boot.common.utils.allocation.TpkEncryptHelper;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Polygon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
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.ZipFile;

@RestController
@Validated
public class ImageController {

    private final ObjectMapper objectMapper = new ObjectMapper();

    private final GeometryFactory geometryFactory = new GeometryFactory();

    private final Logger logger = LoggerFactory.getLogger(ImageController.class);

    private final TaskCellService taskCellService;

    private final ImageService imageService;

    private final TaskInfoService taskInfoService;

    @Value("${tpk.root}")
    private String tpkRoot;

    @Value("${tpkx.root}")
    private String tpkxRoot;

    public ImageController(TaskCellService taskCellService, ImageService imageService, TaskInfoService taskInfoService) {
        this.taskCellService = taskCellService;
        this.imageService = imageService;
        this.taskInfoService = taskInfoService;
    }

    /**
     * 上传影像文件，加密之后存在服务器上
     *
     * @param taskId 任务 ID
     * @param file   影像文件
     */
    @RequestMapping("/image/new")
    @ResponseBody
    public ResultMessage uploadImage(@RequestParam("image") @NotNull(message = "必须上传影像文件")
                                             MultipartFile file,
                                     @RequestParam @Positive
                                             int taskId) throws Exception {
        // 检查上传的文件
        if (file.getSize() == 0) {
            throw new Exception("上传的文件没有内容");
        }
        Optional<String> optionalFilename = Optional.ofNullable(file.getOriginalFilename());
        optionalFilename.orElseThrow(() -> new Exception("非法的文件名"));
        String filename = optionalFilename.get();
        if (!filename.endsWith(".tpk")) {
            throw new Exception("上传的不是 .tpk 文件");
        }
        String tpkFilename = taskId + "_" + filename;
        String tpkxFilename = tpkFilename.substring(0, tpkFilename.lastIndexOf(".") + 1) + "tpkx";
        if (imageService.countByFilename(tpkxFilename) != 0) {
            throw new Exception("存在同名文件，请更改文件名");
        }

        String tpkUrl = tpkRoot + tpkFilename;
        try (InputStream in = file.getInputStream()) {
            // 将 .tpk 文件存在本地
            File tpkFile = new File(tpkUrl);
            if (tpkFile.exists()) {
                Files.delete(Paths.get(tpkUrl));
            }
            Files.copy(in, tpkFile.toPath());
            // 加密
            TpkEncryptHelper.encryptTpk(tpkUrl, tpkxRoot);
            // 解析 TPK 文件，获取经纬度范围
            MapServerInitialExtent extent = getExtentFromTPK(tpkUrl);
            if (extent == null) {
                throw new Exception("无法从 TPK 中获取经纬度范围");
            }
            // 在 task_image 中插入一条新记录
            int id = imageService.insert(new Image(tpkxFilename, taskId, extent.getXmin(), extent.getYmin(), extent.getXmax(), extent.getYmax()));
            ResultMessage<Integer> message = new ResultMessage<>(true, "成功上传影像");
            message.setData(id);
            return message;
        } catch (Exception e) {
            logger.error(e.getMessage());
            return new ResultMessage<>(false, "上传失败");
        } finally {
            // 删除原始 TPK 文件，保证服务器上只保存加密后的文件
            try {
                Files.delete(Paths.get(tpkUrl));
            } catch (Exception e) {
                logger.warn("无法删除 TPK：" + e.getMessage());
            }
        }
    }

    private MapServerInitialExtent getExtentFromTPK(String tpkUrl) throws IOException {
        try (ZipFile zip = new ZipFile(tpkUrl)) {
            for (Enumeration<? extends ZipEntry> e = zip.entries(); e.hasMoreElements(); ) {
                ZipEntry entry = e.nextElement();
                if (!entry.isDirectory()) {
                    if (FilenameUtils.getName(entry.getName()).equals("mapserver.json")) {
                        try (InputStream in = zip.getInputStream(entry)) {
                            MapServerRoot root = JSON.parseObject(IOUtils.toString(in, StandardCharsets.UTF_8), MapServerRoot.class);
                            return root.getContents().getInitialExtent();
                        }
                    }
                }
            }
        }
        throw new IOException("TPK 不包含 mapserver.json");
    }

    /**
     * 获取指定任务的影像文件
     *
     * @param taskId 任务 ID
     * @param page   页码
     * @param count  每页的数据量
     */
    @RequestMapping("/images")
    @ResponseBody
    public ResultMessage getImagesByTaskId(@RequestParam @Positive int taskId,
                                           @RequestParam(defaultValue = "1") @Positive int page,
                                           @RequestParam(defaultValue = "10") @Positive int count) {
        // 统计该任务的影像文件
        int total = imageService.countByTaskId(taskId);
        int totalPages = (int) Math.ceil((double) total / count);
        if (page > totalPages) {
            return new ResultMessage<>(false, "请求的页码大于总页数");
        }

        // 获取指定范围内的数据
        List<Map<String, Object>> images = imageService.findByTaskIdPaginated(taskId, page, count);

        Map<String, Object> data = new HashMap<>();
        data.put("total", totalPages);
        data.put("data", images);
        return new ResultMessage<>(true, "成功获取影像文件列表", data);
    }

    /**
     * 删除影像文件
     *
     * @param str 影像文件 ID 列表字符串，每个 ID 之间用逗号隔开
     */
    @RequestMapping("/image/delete")
    @ResponseBody
    public ResultMessage deleteImages(@RequestParam("ids") @NotBlank String str) {
        List<Integer> ids = Arrays.stream(str.split(",")).mapToInt(Integer::parseInt).boxed().collect(Collectors.toList());
        List<String> filenames = imageService.findFilenamesInIds(ids);
        if (!filenames.isEmpty()) {
            for (String filename : filenames) {
                try {
                    // 删除原始 TPK 文件
                    File tpkFile = new File(tpkRoot + filename.replace(".tpkx", ".tpk"));
                    if (tpkFile.exists()) {
                        if (!tpkFile.delete()) {
                            logger.warn("无法删除 TPK 文件 " + tpkFile.getPath());
                        }
                    }
                    // 删除加密文件
                    File tpkxFile = new File(tpkxRoot + filename);
                    if (tpkxFile.exists()) {
                        if (!tpkxFile.delete()) {
                            logger.warn("无法删除 TPKX 文件 " + tpkxFile.getPath());
                        }
                    }
                } catch (Exception e) {
                    logger.warn(e.getMessage());
                }
            }
        }
        imageService.deleteInIds(ids);
        return new ResultMessage<>(true, "成功删除文件");
    }


    /**
     * 外业人员获取某个任务中与其相关的所有影像
     *
     * @param taskId 任务 ID
     * @param userId 外业人员 ID
     */
    @RequestMapping("/image/all")
    @ResponseBody
    public ResultMessage getImageList(@RequestParam("taskId")
                                      @NotNull(message = "任务 ID 不能为空")
                                              int taskId,
                                      @RequestParam("userId")
                                      @NotNull(message = "人员 ID 不能为空")
                                              int userId) {
        // 获取该任务对应的影像
        List<Image> images = imageService.findByTaskId(taskId);
        if (taskInfoService.isTaskAllocationOptional(taskId)) {
            List<ImageWithUrl> result = images
                    .stream()
                    .map(image -> new ImageWithUrl(image.getId(), "/tpkx/" + image.getFilename(), image.getMinLon(), image.getMinLat(), image.getMaxLon(), image.getMaxLat()))
                    .collect(Collectors.toList());
            return new ResultMessage<>(true, "成功获取影像列表", result);
        }

        // 获取任务涉及的网格
        List<ImageWithUrl> filteredImages = new ArrayList<>();
        List<Polygon> cells = new ArrayList<>();
        for (String geojson : taskCellService.findGeojsonByTaskIdAndUserId(taskId, userId)) {
            Polygon cell = fromGeoJsonToPolygon(geojson);
            if (cell != null) {
                cells.add(cell);
            }
        }
        // 筛选出与任务网格相交的影像
        if (!cells.isEmpty()) {
            for (Image image : images) {
                double xmin = image.getMinLon();
                double ymin = image.getMinLat();
                double xmax = image.getMaxLon();
                double ymax = image.getMaxLat();
                Coordinate[] shell = {new Coordinate(xmin, ymin), new Coordinate(xmin, ymax), new Coordinate(xmax, ymax), new Coordinate(xmax, ymin), new Coordinate(xmin, ymin)};
                Polygon imageBoundary = geometryFactory.createPolygon(shell);
                for (Polygon cell : cells) {
                    if (cell.intersects(imageBoundary)) {
                        filteredImages.add(new ImageWithUrl(image.getId(), "/tpkx/" + image.getFilename(), xmin, ymin, xmax, ymax));
                        break;
                    }
                }
            }
        }
        return new ResultMessage<>(true, "成功获取影像列表", filteredImages);
    }

    /**
     * 将 GeoJSON 字符串转换为 org.locationtech.jts.geom.Polygon 对象
     *
     * @param geojson 表示一个多边形的 GeoJSON 字符串
     * @return org.locationtech.jts.geom.Polygon 对象
     */
    private Polygon fromGeoJsonToPolygon(String geojson) {
        if (geojson == null || geojson.equals("")) {
            return null;
        }
        org.geojson.Feature feature;
        try {
            feature = objectMapper.readValue(geojson, org.geojson.Feature.class);
        } catch (IOException e) {
            logger.error("无法解析数据：{}，数据应为标准的 GeoJSON 格式", geojson);
            return null;
        }
        Coordinate[] shell = ((org.geojson.Polygon) feature.getGeometry())
                .getCoordinates()
                .stream()
                .flatMap(lngLatAlts -> lngLatAlts.stream().map(lngLatAlt -> new Coordinate(lngLatAlt.getLongitude(), lngLatAlt.getLatitude())))
                .toArray(Coordinate[]::new);
        return geometryFactory.createPolygon(shell);
    }

    /**
     * 返回加密影像的 URL
     *
     * @param imageId 影像 ID
     */
    @RequestMapping("/image")
    @ResponseBody
    public ResultMessage getImageUrl(@RequestParam("imageId")
                                     @NotNull(message = "必须上传影像 ID")
                                             int imageId) throws Exception {
        Optional<String> optional = imageService.findFilenameById(imageId);
        if (!optional.isPresent()) {
            throw new Exception("影像不存在");
        }
        return new ResultMessage<>(true, "成功获取影像 URL", "/tpkx/" + optional.get());
    }

    /**
     * 下载加密 TPKX 影像
     *
     * @param filename .tpkx 文件名
     * @throws IOException 读取文件异常
     */
    @RequestMapping("/tpkx/{filename}")
    public ResponseEntity<Resource> downloadImage(@PathVariable("filename") String filename) throws IOException {
        File tpkxFile = new File(tpkxRoot + filename);
        Path path = Paths.get(tpkxFile.getAbsolutePath());
        ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));
        return ResponseEntity
                .ok()
                .contentLength(tpkxFile.length())
                .contentType(MediaType.parseMediaType("application/octet-stream"))
                .body(resource);
    }
}