package com.sundot.airport.item.service;

import com.sundot.airport.common.core.domain.BaseLargeScreenQueryParamDto;
import com.sundot.airport.common.core.domain.BoxPlotDataDto;
import com.sundot.airport.common.core.domain.GroupedBoxPlotDataDto;
import com.sundot.airport.common.core.domain.SeizureLargeScreenHomePageItemDto;
import com.sundot.airport.common.core.domain.entity.SysDept;
import com.sundot.airport.common.enums.DeptTypeEnum;
import com.sundot.airport.common.utils.DateUtils;
import com.sundot.airport.item.mapper.SeizureReportMapper;
import com.sundot.airport.system.service.ISysDeptService;
import com.sundot.airport.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 查获数量分布服务
 * 实现直方图数据计算逻辑
 *
 * @author ruoyi
 * @date 2026-02-24
 */
@Service
public class SeizureDistributionService {


    @Autowired
    private SeizureReportService seizureReportService;

    @Autowired
    private ISysDeptService sysDeptService;

    @Autowired
    private ISysUserService sysUserService;

    @Autowired
    private SeizureReportMapper seizureReportMapper;


    /**
     * 计算查获数量分布直方图数据
     * 分组规则：
     * 1. 确定数据的极差(R) = 最大值 - 最小值
     * 2. 确定组距(h) = R/7
     * 4. 按界限值分组统计查获数量
     *
     * @return 直方图数据列表，每个元素包含：组下限、组上限、该组内安检员人数
     */
    public List<SeizureHistogramData> calculateSeizureDistribution(BaseLargeScreenQueryParamDto dto) {
        // 获取全站范围内所有安检员的查获数量数据
        List<SeizureLargeScreenHomePageItemDto> allData = getSeizureDataForStation(dto.getDeptId(), dto.getStartDate(), dto.getEndDate());

        if (allData == null || allData.isEmpty()) {
            return Collections.emptyList();
        }

        List<BigDecimal> seizureQuantities = allData.stream()
                .map(SeizureLargeScreenHomePageItemDto::getQuantity)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        if (seizureQuantities.isEmpty()) {
            return Collections.emptyList();
        }

        // 1. 确定数据的极差(R) = 最大值 - 最小值
        BigDecimal maxQuantity = seizureQuantities.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
        BigDecimal minQuantity = seizureQuantities.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
        BigDecimal range = maxQuantity.subtract(minQuantity);

        // 2. 确定组距(h) = R/7
        BigDecimal groupWidth = range.divide(new BigDecimal(7), 2, java.math.RoundingMode.HALF_UP);

        // 如果极差为0（所有数据相同），则使用默认组距
        if (range.compareTo(BigDecimal.ZERO) <= 0) {
            groupWidth = new BigDecimal("10");
            minQuantity = minQuantity.subtract(new BigDecimal("5"));
        }

        // 3. 确定各组的界限值
        BigDecimal firstLowerBound = minQuantity;

        // 创建7个组的边界
        List<SeizureHistogramGroup> groups = new ArrayList<>();
        BigDecimal currentLowerBound = firstLowerBound;

        for (int i = 0; i < 7; i++) {
            BigDecimal currentUpperBound = currentLowerBound.add(groupWidth);
            // 对于最后一组，确保上界至少包含最大值
            if (i == 6) {
                BigDecimal adjustedUpperBound = currentUpperBound;
                // 确保最后一组上界至少等于最大值
                if (adjustedUpperBound.compareTo(maxQuantity) < 0) {
                    adjustedUpperBound = maxQuantity;
                }
                groups.add(new SeizureHistogramGroup(currentLowerBound, adjustedUpperBound));
            } else {
                groups.add(new SeizureHistogramGroup(currentLowerBound, currentUpperBound));
            }
            currentLowerBound = currentUpperBound;
        }

        // 4. 按界限值分组统计安检员人数
        List<SeizureHistogramData> result = new ArrayList<>();
        for (int i = 0; i < groups.size(); i++) {
            SeizureHistogramGroup group = groups.get(i);
            int count = 0;
            for (BigDecimal quantity : seizureQuantities) {
                // 最后一组：左闭右闭区间 [lower, upper]
                if (i == groups.size() - 1) {
                    if (quantity.compareTo(group.lowerBound) >= 0 && quantity.compareTo(group.upperBound) <= 0) {
                        count++;
                    }
                } else {
                    // 其他组：左闭右开区间 [lower, upper)
                    if (quantity.compareTo(group.lowerBound) >= 0 && quantity.compareTo(group.upperBound) < 0) {
                        count++;
                    }
                }
            }
            result.add(new SeizureHistogramData(
                    group.lowerBound.setScale(2, java.math.RoundingMode.HALF_UP),
                    group.upperBound.setScale(2, java.math.RoundingMode.HALF_UP),
                    count
            ));
        }

        return result;
    }

    /**
     * 获取指定站点的查获数据（全站级别）
     */
    private List<SeizureLargeScreenHomePageItemDto> getSeizureDataForStation(Long deptId, Date startDate, Date endDate) {
        // 创建查询参数
        BaseLargeScreenQueryParamDto dto = new BaseLargeScreenQueryParamDto();
        dto.setDeptId(deptId);
        dto.setStartDate(startDate);
        dto.setEndDate(endDate);

        return seizureReportService.getSeizureLargeScreenHomePageItemDtoList(dto);
    }

    /**
     * 获取指定科室的查获数据（科室级别）
     *
     * @param brigadeId 科室ID
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @return 科室级别查获数据列表
     */
    private List<SeizureLargeScreenHomePageItemDto> getSeizureDataForBrigade(Long brigadeId, Date startDate, Date endDate) {
        if (brigadeId == null) {
            return Collections.emptyList();
        }
        // 创建查询参数
        BaseLargeScreenQueryParamDto dto = new BaseLargeScreenQueryParamDto();
        dto.setInspectBrigadeId(brigadeId);
        dto.setStartDate(startDate);
        dto.setEndDate(DateUtils.addDays(endDate, 1));
        return seizureReportService.getSeizureLargeScreenHomePinspectBrigadeIdList(dto);
    }

    /**
     * 直方图数据类
     */
    public static class SeizureHistogramData {
        /**
         * 直方图组的下限值
         */
        private BigDecimal lowerBound;
        /**
         * 直方图组的上限值
         */
        private BigDecimal upperBound;
        /**
         * 该组内的数据数量
         */
        private int count;

        public SeizureHistogramData(BigDecimal lowerBound, BigDecimal upperBound, int count) {
            this.lowerBound = lowerBound;
            this.upperBound = upperBound;
            this.count = count;
        }

        public BigDecimal getLowerBound() {
            return lowerBound;
        }

        public void setLowerBound(BigDecimal lowerBound) {
            this.lowerBound = lowerBound;
        }

        public BigDecimal getUpperBound() {
            return upperBound;
        }

        public void setUpperBound(BigDecimal upperBound) {
            this.upperBound = upperBound;
        }

        public int getCount() {
            return count;
        }

        public void setCount(int count) {
            this.count = count;
        }
    }

    /**
     * 组边界类
     */
    private static class SeizureHistogramGroup {
        BigDecimal lowerBound;
        BigDecimal upperBound;

        SeizureHistogramGroup(BigDecimal lowerBound, BigDecimal upperBound) {
            this.lowerBound = lowerBound;
            this.upperBound = upperBound;
        }
    }

    /**
     * 计算箱线图数据
     * 箱线图共由五个数值点构成，分别是最小观察值，25%分位数(Q1)，中位数，75%分位数(Q3)，最大观察值。
     * 最小观察值=Q1-1.5(IQR),IQR=Q3-Q1
     * 最大观察值=Q3+1.5(IQR),IQR=Q3-Q1
     * 箱线图中，下限为最小观察值与真实最小值之间的较大值;上限为最大观察值与最大值之间的较小值。
     * 如果数据存在离群点即异常值，他们大于超出最大或者最小观察值，此时此将离群点以"圆点"形式进行展示。
     *
     * @param dto 查询参数
     * @return 箱线图数据列表（按科室分组）
     */
    public List<GroupedBoxPlotDataDto> calculateBoxPlotData(BaseLargeScreenQueryParamDto dto) {
        if (dto.getInspectStationId() == null) {
            return Collections.emptyList();
        }

        // 获取站点信息（站级）
        SysDept stationDept = sysDeptService.selectDeptById(dto.getInspectStationId());
        if (stationDept == null) {
            return Collections.emptyList();
        }

        // 获取该站点下的所有大队
        List<SysDept> brigades = getBrigadesByStation(dto.getInspectStationId());
        // 获取该站点下的所有科室
        List<SysDept> departments = getDepartmentsByStation(dto.getInspectStationId());

        // 构建所有分组：先添加站级，再添加大队级
        List<SysDept> allGroups = new ArrayList<>();
        allGroups.add(stationDept); // 添加站级
        allGroups.addAll(brigades); // 添加大队级
        allGroups.addAll(departments); // 添加科室级

        // 获取查获数量数据
        List<SeizureLargeScreenHomePageItemDto> seizureReportList = getSeizureDataForStation(dto.getDeptId(), dto.getStartDate(), dto.getEndDate());
        if (seizureReportList == null || seizureReportList.isEmpty()) {
            return Collections.emptyList();
        }

        // 按部门分组数据
        Map<String, List<BigDecimal>> groupedQuantities = new HashMap<>();

        // 首先处理站级数据（全站范围）
        List<BigDecimal> stationQuantities = new ArrayList<>();
        for (SeizureLargeScreenHomePageItemDto report : seizureReportList) {
            BigDecimal quantity = report.getQuantity();
            if (quantity != null) {
                stationQuantities.add(quantity);
            }
        }
        groupedQuantities.put(stationDept.getDeptName(), stationQuantities);

        // 然后处理大队级数据
        for (SysDept dept : brigades) {
            List<SeizureLargeScreenHomePageItemDto> brigadeSeizureReportList = getSeizureDataForBrigade(dept.getDeptId(), dto.getStartDate(), dto.getEndDate());
            List<BigDecimal> deptQuantities = brigadeSeizureReportList.stream().map(SeizureLargeScreenHomePageItemDto::getQuantity).filter(Objects::nonNull).collect(Collectors.toList());
            groupedQuantities.put(dept.getDeptName(), deptQuantities);
        }

        // 然后处理科室级数据
        for (SysDept dept : departments) {
            List<SeizureLargeScreenHomePageItemDto> departmentSeizureReportList = getSeizureDataForDepartment(dept.getDeptId(), dto.getStartDate(), dto.getEndDate());
            List<BigDecimal> deptQuantities = departmentSeizureReportList.stream().map(SeizureLargeScreenHomePageItemDto::getQuantity).filter(Objects::nonNull).collect(Collectors.toList());
            groupedQuantities.put(dept.getDeptName(), deptQuantities);
        }

        // 为每个分组计算箱线图数据
        List<GroupedBoxPlotDataDto> result = new ArrayList<>();

        for (SysDept group : allGroups) {
            String groupName = group.getDeptName();
            List<BigDecimal> quantities = groupedQuantities.getOrDefault(groupName, Collections.emptyList());

            List<BigDecimal> seizureQuantities = quantities.stream()
                    .filter(Objects::nonNull)
                    .sorted()
                    .collect(Collectors.toList());

            BoxPlotDataDto boxPlotData = new BoxPlotDataDto();
            if (seizureQuantities.isEmpty()) {
                boxPlotData.setTotalCount(0);
            } else {
                int size = seizureQuantities.size();

                // 计算四分位数
                BigDecimal q1 = calculateQuartile(seizureQuantities, 0.25);
                BigDecimal median = calculateQuartile(seizureQuantities, 0.5);
                BigDecimal q3 = calculateQuartile(seizureQuantities, 0.75);

                // 计算IQR
                BigDecimal iqr = q3.subtract(q1);

                // 计算最小观察值和最大观察值
                BigDecimal minObservation = q1.subtract(iqr);
                BigDecimal maxObservation = q3.add(iqr);

                // 获取真实最小值和最大值
                BigDecimal realMin = seizureQuantities.get(0);
                BigDecimal realMax = seizureQuantities.get(size - 1);

                // 确定箱线图的下限和上限
                BigDecimal lowerBound = minObservation.compareTo(realMin) > 0 ? minObservation : realMin;
                BigDecimal upperBound = maxObservation.compareTo(realMax) < 0 ? maxObservation : realMax;

                // 识别离群点
                List<BigDecimal> outliers = new ArrayList<>();
                List<BigDecimal> nonOutliers = new ArrayList<>();

                for (BigDecimal quantity : seizureQuantities) {
                    if (quantity.compareTo(lowerBound) < 0 || quantity.compareTo(upperBound) > 0) {
                        outliers.add(quantity);
                    } else {
                        nonOutliers.add(quantity);
                    }
                }

                // 设置箱线图数据
                boxPlotData.setMinObservation(minObservation);
                boxPlotData.setQ1(q1);
                boxPlotData.setMedian(median);
                boxPlotData.setQ3(q3);
                boxPlotData.setMaxObservation(maxObservation);
                boxPlotData.setLowerBound(lowerBound);
                boxPlotData.setUpperBound(upperBound);
                boxPlotData.setOutliers(outliers);
                boxPlotData.setNonOutliers(nonOutliers);
                boxPlotData.setTotalCount(size);
            }

            result.add(new GroupedBoxPlotDataDto(groupName, boxPlotData));
        }

        return result;
    }


    /**
     * 获取指定站点下的所有大队
     *
     * @param stationDeptId 站点部门ID
     * @return 大队列表
     */
    private List<SysDept> getBrigadesByStation(Long stationDeptId) {
        if (stationDeptId == null) {
            return Collections.emptyList();
        }

        // 查询站点下的所有子部门
        List<SysDept> childrenDepts = sysDeptService.selectChildrenDeptById(stationDeptId);

        // 过滤出大队级别的部门
        return childrenDepts.stream()
                .filter(dept -> DeptTypeEnum.BRIGADE.getCode().equals(dept.getDeptType()))
                .collect(Collectors.toList());
    }

    /**
     * 计算指定分位数的值
     * 使用线性插值法
     *
     * @param data     数据列表（已排序）
     * @param quartile 分位数（0.25, 0.5, 0.75）
     * @return 分位数值
     */
    private BigDecimal calculateQuartile(List<BigDecimal> data, double quartile) {
        int n = data.size();
        if (n == 0) {
            return BigDecimal.ZERO;
        }

        // 计算位置
        double position = quartile * (n + 1);
        int floorPosition = (int) Math.floor(position);
        int ceilPosition = (int) Math.ceil(position);

        // 如果位置是整数，直接取该位置的值
        if (floorPosition == ceilPosition) {
            if (floorPosition <= 0) {
                return data.get(0);
            } else if (floorPosition > n) {
                return data.get(n - 1);
            } else {
                return data.get(floorPosition - 1);
            }
        }

        // 否则使用线性插值
        if (floorPosition <= 0) {
            return data.get(0);
        } else if (ceilPosition > n) {
            return data.get(n - 1);
        } else {
            BigDecimal lowerValue = data.get(floorPosition - 1);
            BigDecimal upperValue = data.get(ceilPosition - 1);
            double fraction = position - floorPosition;
            return lowerValue.add(upperValue.subtract(lowerValue).multiply(BigDecimal.valueOf(fraction)));
        }
    }

    /**
     * 获取指定站点下的所有科室
     *
     * @param stationDeptId 站点部门ID
     * @return 大队列表
     */
    private List<SysDept> getDepartmentsByStation(Long stationDeptId) {
        if (stationDeptId == null) {
            return Collections.emptyList();
        }

        // 查询站点下的所有子部门
        List<SysDept> childrenDepts = sysDeptService.selectChildrenDeptById(stationDeptId);

        // 过滤出大队级别的部门
        return childrenDepts.stream()
                .filter(dept -> DeptTypeEnum.MANAGER.getCode().equals(dept.getDeptType()))
                .collect(Collectors.toList());
    }

    /**
     * 获取指定科室的查获数据（科室级别）
     *
     * @param departmentId 科室ID
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @return 科室级别查获数据列表
     */
    private List<SeizureLargeScreenHomePageItemDto> getSeizureDataForDepartment(Long departmentId, Date startDate, Date endDate) {
        if (departmentId == null) {
            return Collections.emptyList();
        }
        // 创建查询参数
        BaseLargeScreenQueryParamDto dto = new BaseLargeScreenQueryParamDto();
        dto.setInspectDepartmentId(departmentId);
        dto.setStartDate(startDate);
        dto.setEndDate(DateUtils.addDays(endDate, 1));
        return seizureReportService.getSeizureLargeScreenHomePinspectBrigadeIdList(dto);
    }
}