package com.sz.admin.monitor.utils;

import com.alibaba.fastjson.JSON;
import com.sz.admin.monitor.pojo.vo.offset.OffsetVO;
import com.sz.core.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.indexer.DoubleIndexer;
import org.bytedeco.javacpp.indexer.FloatIndexer;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_features2d.BFMatcher;
import org.bytedeco.opencv.opencv_features2d.ORB;
import org.bytedeco.opencv.opencv_imgproc.CLAHE;

import java.io.File;
import java.util.*;
import java.util.Arrays;

import static org.bytedeco.opencv.global.opencv_calib3d.RANSAC;
import static org.bytedeco.opencv.global.opencv_calib3d.estimateAffinePartial2D;
import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imread;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
import static org.bytedeco.opencv.global.opencv_photo.fastNlMeansDenoising;
import static org.bytedeco.opencv.global.opencv_video.calcOpticalFlowFarneback;

/**
 * ClassName: RobustImageMatcherUtil
 * Package: com.sz.admin.monitor.utils
 * Description: 图片移位和检测工具类（生产环境优化版）
 */
@Slf4j
public class RobustImageMatcherUtil {

    public static class MatchResult {
        public String method;
        public boolean success = false;
        public double offsetX = 0;
        public double offsetY = 0;
        public double confidence = 0;

        public MatchResult(String method) {
            this.method = method;
        }
    }

    private static Mat[] preprocess(Mat img1, Mat img2) {
        Mat gray1 = new Mat();
        Mat gray2 = new Mat();
        cvtColor(img1, gray1, COLOR_BGR2GRAY);
        cvtColor(img2, gray2, COLOR_BGR2GRAY);

        if (gray1.cols() != gray2.cols() || gray1.rows() != gray2.rows()) {
            Mat resizedGray2 = new Mat();
            resize(gray2, resizedGray2, new Size(gray1.cols(), gray1.rows()));
            gray2 = resizedGray2;
        }

        Mat denoised1 = new Mat();
        Mat denoised2 = new Mat();
        fastNlMeansDenoising(gray1, denoised1, 3.0f, 7, 21);
        fastNlMeansDenoising(gray2, denoised2, 3.0f, 7, 21);

        CLAHE clahe = createCLAHE(2.0, new Size(8, 8));
        Mat final1 = new Mat();
        Mat final2 = new Mat();
        clahe.apply(denoised1, final1);
        clahe.apply(denoised2, final2);

        // 注意：无需手动 close，外层的 PointerScope 会在整个流程结束时自动且安全地回收所有对象
        return new Mat[]{final1, final2};
    }

    private static boolean isSameScene(Mat gray1, Mat gray2) {
        Mat hist1 = new Mat();
        Mat hist2 = new Mat();

        IntPointer channels = new IntPointer(1).put(0);
        IntPointer histSize = new IntPointer(1).put(256);
        FloatPointer ranges = new FloatPointer(2).put(0.0f, 256.0f);
        PointerPointer<Pointer> histRanges = new PointerPointer<>(1).put(ranges);

        Mat mask = new Mat();

        calcHist(gray1, 1, channels, mask, hist1, 1, histSize, histRanges, true, false);
        calcHist(gray2, 1, channels, mask, hist2, 1, histSize, histRanges, true, false);

        normalize(hist1, hist1, 0, 1, NORM_MINMAX, -1, new Mat());
        normalize(hist2, hist2, 0, 1, NORM_MINMAX, -1, new Mat());

        double distance = compareHist(hist1, hist2, HISTCMP_BHATTACHARYYA);

        return distance < 0.5;
    }

    private static MatchResult calcOpticalFlow(Mat gray1, Mat gray2) {
        MatchResult result = new MatchResult("optical_flow");
        Mat flow = new Mat();
        calcOpticalFlowFarneback(gray1, gray2, flow, 0.5, 3, 15, 3, 5, 1.2, 0);

        FloatIndexer flowIdx = flow.createIndexer();
        try {
            List<Double> dxList = new ArrayList<>();
            List<Double> dyList = new ArrayList<>();

            for (int y = 0; y < flow.rows(); y += 2) {
                for (int x = 0; x < flow.cols(); x += 2) {
                    double dx = flowIdx.get(y, x, 0);
                    double dy = flowIdx.get(y, x, 1);
                    if (Math.abs(dx) < 100 && Math.abs(dy) < 100) {
                        dxList.add(dx);
                        dyList.add(dy);
                    }
                }
            }

            if (dxList.size() < 100) return result;

            double stdDevX = calculateStdDev(dxList);
            double stdDevY = calculateStdDev(dyList);
            double avgStdDev = (stdDevX + stdDevY) / 2.0;

            Collections.sort(dxList);
            Collections.sort(dyList);
            result.offsetX = dxList.get(dxList.size() / 2);
            result.offsetY = dyList.get(dyList.size() / 2);

            if (avgStdDev > 3.0) {
                return result;
            }

            result.success = true;
            result.confidence = Math.max(0.1, 1.0 - (avgStdDev / 3.0));

        } finally {
            // Indexer 比较特殊，建议手动 release 释放视图映射
            flowIdx.release();
        }
        return result;
    }

    private static double calculateStdDev(List<Double> list) {
        if (list.isEmpty()) return 0.0;
        double sum = 0.0, sqSum = 0.0;
        for (double num : list) {
            sum += num;
        }
        double mean = sum / list.size();
        for (double num : list) {
            sqSum += Math.pow(num - mean, 2);
        }
        return Math.sqrt(sqSum / list.size());
    }

    private static MatchResult gridMotionStatistics(Mat gray1, Mat gray2) {
        MatchResult result = new MatchResult("grid_matching");
        int h = gray1.rows();
        int w = gray1.cols();
        int cellH = h / 10;
        int cellW = w / 10;

        List<Double> dxList = new ArrayList<>();
        List<Double> dyList = new ArrayList<>();
        double totalScore = 0;

        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                int y1 = i * cellH, x1 = j * cellW;
                int y2 = Math.min((i + 1) * cellH, h);
                int x2 = Math.min((j + 1) * cellW, w);

                int margin = 5;
                Rect roi1 = new Rect(Math.max(0, x1 - margin), Math.max(0, y1 - margin),
                    Math.min(w - x1 + margin, x2 - x1 + 2 * margin),
                    Math.min(h - y1 + margin, y2 - y1 + 2 * margin));

                int searchRange = 30;
                Rect roi2 = new Rect(Math.max(0, x1 - searchRange), Math.max(0, y1 - searchRange),
                    Math.min(w - x1 + searchRange, x2 - x1 + 2 * searchRange),
                    Math.min(h - y1 + searchRange, y2 - y1 + 2 * searchRange));

                if (roi1.width() <= 0 || roi1.height() <= 0 || roi2.width() <= roi1.width() || roi2.height() <= roi1.height()) {
                    continue;
                }

                Mat template = new Mat(gray1, roi1);
                Mat searchArea = new Mat(gray2, roi2);
                Mat matchRes = new Mat();

                matchTemplate(searchArea, template, matchRes, TM_CCOEFF_NORMED);

                DoublePointer minVal = new DoublePointer(1);
                DoublePointer maxVal = new DoublePointer(1);
                Point minLoc = new Point();
                Point maxLoc = new Point();

                minMaxLoc(matchRes, minVal, maxVal, minLoc, maxLoc, null);

                if (maxVal.get() > 0.6) {
                    dxList.add((double) ((roi2.x() + maxLoc.x()) - roi1.x()));
                    dyList.add((double) ((roi2.y() + maxLoc.y()) - roi1.y()));
                    totalScore += maxVal.get();
                }
            }
        }

        if (dxList.size() > 5) {
            double stdDevX = calculateStdDev(dxList);
            double stdDevY = calculateStdDev(dyList);
            double avgStdDev = (stdDevX + stdDevY) / 2.0;

            if (avgStdDev < 5.0) {
                Collections.sort(dxList);
                Collections.sort(dyList);
                result.success = true;
                result.offsetX = dxList.get(dxList.size() / 2);
                result.offsetY = dyList.get(dyList.size() / 2);

                double avgScore = totalScore / dxList.size();
                result.confidence = avgScore * Math.max(0.1, 1.0 - (avgStdDev / 5.0));
            }
        }
        return result;
    }

    private static MatchResult robustFeatureMatch(Mat gray1, Mat gray2) {
        MatchResult result = new MatchResult("feature_matching");
        ORB orb = ORB.create(5000, 1.2f, 8, 31, 0, 2, ORB.HARRIS_SCORE, 31, 20);

        KeyPointVector kp1 = new KeyPointVector();
        KeyPointVector kp2 = new KeyPointVector();
        Mat desc1 = new Mat();
        Mat desc2 = new Mat();

        orb.detectAndCompute(gray1, new Mat(), kp1, desc1, false);
        orb.detectAndCompute(gray2, new Mat(), kp2, desc2, false);

        if (kp1.size() < 10 || kp2.size() < 10 || desc1.empty() || desc2.empty()) {
            return result;
        }

        BFMatcher matcher = new BFMatcher(NORM_HAMMING, true);
        DMatchVector matches = new DMatchVector();
        matcher.match(desc1, desc2, matches);

        long numMatches = matches.size();
        if (numMatches < 10) return result;

        Mat srcPts = new Mat((int)numMatches, 1, CV_32FC2);
        Mat dstPts = new Mat((int)numMatches, 1, CV_32FC2);
        FloatIndexer srcIdx = srcPts.createIndexer();
        FloatIndexer dstIdx = dstPts.createIndexer();

        try {
            for (int i = 0; i < numMatches; i++) {
                Point2f p1 = kp1.get(matches.get(i).queryIdx()).pt();
                Point2f p2 = kp2.get(matches.get(i).trainIdx()).pt();
                srcIdx.put(i, 0, 0, p1.x());
                srcIdx.put(i, 0, 1, p1.y());
                dstIdx.put(i, 0, 0, p2.x());
                dstIdx.put(i, 0, 1, p2.y());
            }
        } finally {
            srcIdx.release();
            dstIdx.release();
        }

        Mat inliers = new Mat();
        Mat affine = estimateAffinePartial2D(srcPts, dstPts, inliers, RANSAC, 5.0, 2000, 0.99, 10);

        if (!affine.empty()) {
            DoubleIndexer affIdx = affine.createIndexer();
            try {
                result.offsetX = affIdx.get(0, 2);
                result.offsetY = affIdx.get(1, 2);

                int inlierCount = countNonZero(inliers);
                double ratio = (double) inlierCount / numMatches;

                if (ratio > 0.3) {
                    result.success = true;
                    result.confidence = ratio;
                }
            } finally {
                affIdx.release();
            }
        }

        return result;
    }

    private static MatchResult consistencyCheck(List<MatchResult> results) {
        List<MatchResult> valids = new ArrayList<>();
        for (MatchResult r : results) {
            if (r.success) valids.add(r);
        }

        if (valids.isEmpty()) return new MatchResult("none");
        if (valids.size() == 1) return valids.get(0);

        MatchResult finalRes = new MatchResult("fusion");
        double sumX = 0, sumY = 0, sumConf = 0;
        List<String> methods = new ArrayList<>();

        for (MatchResult r : valids) {
            sumX += r.offsetX * r.confidence;
            sumY += r.offsetY * r.confidence;
            sumConf += r.confidence;
            methods.add(r.method);
        }

        finalRes.success = true;
        finalRes.offsetX = sumX / sumConf;
        finalRes.offsetY = sumY / sumConf;
        finalRes.method = String.join("+", methods);
        finalRes.confidence = Math.min(1.0, (sumConf / valids.size()) * 1.1);
        return finalRes;
    }

    private static Map<String, Object> formatResult(MatchResult result, Mat img1, Mat img2) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("type", "tx_yzwpy");

        if (!result.success || result.confidence < 0.2) {
            log.warn("图像配准失败或置信度过低");
            map.put("value", 1);
            map.put("code", "4000");
            OffsetVO offsetVO = new OffsetVO();
            offsetVO.setDescription("图像存在偏移");
            map.put("description", JsonUtils.toJsonString(offsetVO));
            return map;
        }

        double dx = result.offsetX * 2;
        double dy = result.offsetY * 2;

        int status = (Math.abs(dx) < 5 && Math.abs(dy) < 5 && result.confidence > 0.5) ? 0 : 1;

        map.put("value", status);
        // 注：原代码此处为 status >= 0，由于 status 只能是 0 或 1，此处必定为 "2000"，这里保持原逻辑。
        map.put("code", status >= 0 ? "2000" : "4000");
        map.put("pos", Arrays.asList((int) Math.round(dx), (int) Math.round(dy)));
        map.put("conf", Double.parseDouble(String.format("%.3f", result.confidence)));
        String dxStr = String.format("%.2f", dx);
        String dyStr = String.format("%.2f", dy);
        OffsetVO offsetVO = new OffsetVO();
        offsetVO.setX(dxStr);
        offsetVO.setY(dyStr);
        offsetVO.setDescription(status == 0 ? "图像基本一致" : "图像存在偏移");
        offsetVO.setUnit("px");
        map.put("description", JsonUtils.toJsonString(offsetVO));
        return map;
    }

    /**
     * 计算两张图像的偏移量 (生产环境安全入口)
     */
    public static Map<String, Object> calculateOffset(String path1, String path2) {
        if (StringUtils.isBlank(path1) || StringUtils.isBlank(path2)) {
            throw new RuntimeException("图像路径不能为空");
        }

        File file1 = new File(path1);
        File file2 = new File(path2);
        if (!file1.exists() || !file2.exists()) {
            log.error("图像文件不存在: path1={}, path2={}", path1, path2);
            throw new RuntimeException("参与对比的图像文件不存在");
        }

        // 核心改造：使用 PointerScope 包裹整个处理流程
        // 任何在这个 try 块内创建的 JavaCPP 对象(Mat, Pointer等)，都会在退出大括号时自动释放底层 C++ 内存。
        try (PointerScope scope = new PointerScope()) {

            Mat img1 = imread(path1);
            Mat img2 = imread(path2);

            if (img1.empty() || img2.empty()) {
                log.error("无法加载图片内容: path1={}, path2={}", path1, path2);
                throw new RuntimeException("无法加载图片内容，文件可能已损坏");
            }

            Mat smallImg1 = new Mat();
            Mat smallImg2 = new Mat();
            resize(img1, smallImg1, new Size(), 0.5, 0.5, INTER_LINEAR);
            resize(img2, smallImg2, new Size(), 0.5, 0.5, INTER_LINEAR);

            Mat[] preprocessed = preprocess(smallImg1, smallImg2);
            Mat gray1 = preprocessed[0];
            Mat gray2 = preprocessed[1];

            if (!isSameScene(gray1, gray2)) {
                log.warn("图像差异过大，非同一场景: path1={}, path2={}", path1, path2);
                return null;
            }

            MatchResult flowResult = calcOpticalFlow(gray1, gray2);
            MatchResult gridResult = gridMotionStatistics(gray1, gray2);
            MatchResult featureResult = robustFeatureMatch(gray1, gray2);

            MatchResult finalResult = consistencyCheck(Arrays.asList(flowResult, gridResult, featureResult));

            return formatResult(finalResult, img1, img2);

        } catch (Exception e) {
            log.error("计算图像偏移量时发生异常: ", e);
            throw new RuntimeException("图像偏移计算失败: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        // 图片完全不同 value 1
//        String img1Path = "D:\\work\\images\\CAPTURE_118_20260306140433490_7c323ce1.jpg";
//        String img2Path = "D:\\work\\images\\CAPTURE_118_20260306140650426_556115aa.jpg";
        // 相同的图片  value 0
//        String img1Path = "D:\\work\\images\\CAPTURE_118_20260306140433490_7c323ce1.jpg";
//        String img2Path = "D:\\work\\images\\CAPTURE_118_20260306140555121_0a31347e.jpg";
        // 出现移位的图片 value 1
          String img1Path="D:\\work\\images\\CAPTURE_118_20260306140328178_7f050439.jpg";
          String img2Path="D:\\work\\images\\CAPTURE_118_20260306140433490_7c323ce1.jpg";
        Map<String, Object> map = RobustImageMatcherUtil.calculateOffset(img1Path, img2Path);
        System.out.println(map);
    }
}