package com.gxwebsoft.shop.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 电子围栏(多边形)相关工具：
 * - points 字符串解析（兼容常见格式：JSON数组 / "lng,lat;lng,lat" / 纯数字逗号序列）
 * - 点在多边形内判断（边界视为“在围栏内”）
 */
public final class GeoFenceUtil {

    private GeoFenceUtil() {
    }

    public static final class Point {
        public final double lng;
        public final double lat;

        public Point(double lng, double lat) {
            this.lng = lng;
            this.lat = lat;
        }
    }

    private static final ObjectMapper OM = new ObjectMapper();

    /**
     * 解析围栏 points 字段为多边形点列表。
     * <p>
     * 支持格式示例：
     * - [[lng,lat],[lng,lat],...]
     * - [{"lng":..,"lat":..}, ...] / [{"longitude":..,"latitude":..}, ...]
     * - "lng,lat;lng,lat;..."
     * - "lng,lat|lng,lat|..."
     * - "lng,lat,lng,lat,lng,lat,..."（纯逗号序列，偶数个数字）
     */
    public static List<Point> parsePolygonPoints(String pointsRaw) {
        if (pointsRaw == null) {
            return Collections.emptyList();
        }
        String s = pointsRaw.trim();
        if (s.isEmpty()) {
            return Collections.emptyList();
        }

        // JSON array
        if (s.startsWith("[")) {
            try {
                JsonNode root = OM.readTree(s);
                if (!root.isArray()) {
                    throw new IllegalArgumentException("points 不是数组");
                }
                List<Point> pts = new ArrayList<>();
                for (JsonNode node : root) {
                    if (node == null || node.isNull()) {
                        continue;
                    }
                    if (node.isArray() && node.size() >= 2) {
                        double a = node.get(0).asDouble();
                        double b = node.get(1).asDouble();
                        pts.add(normalizeLngLat(a, b));
                        continue;
                    }
                    if (node.isObject()) {
                        JsonNode lngNode = node.get("lng");
                        if (lngNode == null) lngNode = node.get("longitude");
                        JsonNode latNode = node.get("lat");
                        if (latNode == null) latNode = node.get("latitude");
                        if (lngNode == null || latNode == null) {
                            throw new IllegalArgumentException("points 对象缺少 lng/lat 字段");
                        }
                        pts.add(normalizeLngLat(lngNode.asDouble(), latNode.asDouble()));
                        continue;
                    }
                    throw new IllegalArgumentException("points 数组元素格式不支持");
                }
                return trimClosingPoint(pts);
            } catch (Exception e) {
                throw new IllegalArgumentException("points JSON 解析失败: " + e.getMessage(), e);
            }
        }

        // 非 JSON：尝试按分隔符拆分
        String normalized = s.replace("\n", "").replace("\r", "").trim();
        String[] pairTokens;
        if (normalized.contains(";")) {
            pairTokens = normalized.split(";+");
            return trimClosingPoint(parsePairs(pairTokens));
        }
        if (normalized.contains("|")) {
            pairTokens = normalized.split("\\|+");
            return trimClosingPoint(parsePairs(pairTokens));
        }

        // 纯逗号序列：lng,lat,lng,lat,...
        if (normalized.contains(",")) {
            String[] nums = normalized.split(",+");
            List<Double> ds = new ArrayList<>();
            for (String num : nums) {
                String t = num.trim();
                if (t.isEmpty()) continue;
                ds.add(Double.parseDouble(t));
            }
            if (ds.size() % 2 != 0) {
                throw new IllegalArgumentException("points 逗号序列数字个数必须为偶数");
            }
            List<Point> pts = new ArrayList<>();
            for (int i = 0; i < ds.size(); i += 2) {
                pts.add(normalizeLngLat(ds.get(i), ds.get(i + 1)));
            }
            return trimClosingPoint(pts);
        }

        throw new IllegalArgumentException("points 格式不支持");
    }

    private static List<Point> parsePairs(String[] pairTokens) {
        List<Point> pts = new ArrayList<>();
        for (String token : pairTokens) {
            if (token == null) continue;
            String t = token.trim();
            if (t.isEmpty()) continue;
            String[] parts = t.split("[,\\s]+");
            if (parts.length < 2) {
                throw new IllegalArgumentException("points 点格式错误: " + t);
            }
            double a = Double.parseDouble(parts[0].trim());
            double b = Double.parseDouble(parts[1].trim());
            pts.add(normalizeLngLat(a, b));
        }
        return pts;
    }

    /**
     * 归一化输入的两个数为 (lng,lat)。
     * 若可明显判断是 (lat,lng) 则自动交换（仅在一个维度超出经纬常规范围时触发）。
     */
    private static Point normalizeLngLat(double first, double second) {
        boolean firstLooksLat = Math.abs(first) <= 90;
        boolean secondLooksLat = Math.abs(second) <= 90;
        boolean firstLooksLng = Math.abs(first) <= 180;
        boolean secondLooksLng = Math.abs(second) <= 180;

        // 明显是 (lat,lng)：first 像纬度且 second 像经度，但 second 不像纬度
        if (firstLooksLat && secondLooksLng && !secondLooksLat) {
            return new Point(second, first);
        }
        // 默认按 (lng,lat)
        if (!firstLooksLng || !secondLooksLat) {
            // 仍返回默认顺序，让上层决定是否拒单（避免隐式容错导致误判）
            return new Point(first, second);
        }
        return new Point(first, second);
    }

    private static List<Point> trimClosingPoint(List<Point> pts) {
        if (pts == null || pts.size() < 2) {
            return pts == null ? Collections.emptyList() : pts;
        }
        Point first = pts.get(0);
        Point last = pts.get(pts.size() - 1);
        if (nearlyEqual(first.lng, last.lng, 1e-12) && nearlyEqual(first.lat, last.lat, 1e-12)) {
            return new ArrayList<>(pts.subList(0, pts.size() - 1));
        }
        return pts;
    }

    /**
     * 点在多边形内判断（ray casting），边界视为 inside。
     */
    public static boolean containsInclusive(List<Point> polygon, double lng, double lat) {
        if (polygon == null || polygon.size() < 3) {
            return false;
        }
        final double eps = 1e-12;
        boolean inside = false;
        int n = polygon.size();
        for (int i = 0, j = n - 1; i < n; j = i++) {
            Point pi = polygon.get(i);
            Point pj = polygon.get(j);

            // 边界：点在线段上
            if (pointOnSegment(lng, lat, pj.lng, pj.lat, pi.lng, pi.lat, eps)) {
                return true;
            }

            // 射线与边相交：以 lat 为 y
            boolean intersect = ((pi.lat > lat) != (pj.lat > lat))
                    && (lng < (pj.lng - pi.lng) * (lat - pi.lat) / (pj.lat - pi.lat + 0.0) + pi.lng);
            if (intersect) {
                inside = !inside;
            }
        }
        return inside;
    }

    private static boolean pointOnSegment(double px, double py,
                                          double ax, double ay,
                                          double bx, double by,
                                          double eps) {
        // 叉积为 0 表示共线
        double cross = (px - ax) * (by - ay) - (py - ay) * (bx - ax);
        if (Math.abs(cross) > eps) {
            return false;
        }
        // 点积判断是否在线段范围内
        double dot = (px - ax) * (bx - ax) + (py - ay) * (by - ay);
        if (dot < -eps) {
            return false;
        }
        double lenSq = (bx - ax) * (bx - ax) + (by - ay) * (by - ay);
        return dot - lenSq <= eps;
    }

    private static boolean nearlyEqual(double a, double b, double eps) {
        return Math.abs(a - b) <= eps;
    }
}

