package com.aucma.report.service.impl;

import com.aucma.report.domain.DeviceFaultAnalysisReport;
import com.aucma.report.domain.DeviceOeeReport;
import com.aucma.report.domain.RepairHoursReport;
import com.aucma.report.mapper.DmsReportMapper;
import com.aucma.report.service.IDmsReportService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * DMS 设备报表 Service 实现
 */
@Service
public class DmsReportServiceImpl implements IDmsReportService {

    @Autowired
    private DmsReportMapper dmsReportMapper;

    @Override
    public List<DeviceFaultAnalysisReport> deviceFaultAnalysisList(Map<String, Object> params) {
        // 直接透传到 Mapper，不在 Service 层做二次加工：
        // 故障分析报表的聚合逻辑全部写在 SQL 里（分组、计数、TOP-N），Service 只做转发。
        return dmsReportMapper.deviceFaultAnalysisList(params);
    }

    @Override
    public List<RepairHoursReport> repairHoursReportList(Map<String, Object> params) {
        // 维修工时报表同样是纯 SQL 聚合，Service 不做额外处理，直接返回 Mapper 结果。
        return dmsReportMapper.repairHoursReportList(params);
    }

    @Override
    public List<DeviceOeeReport> deviceOeeReportList(Map<String, Object> params) {
        // 第一步：先从 DMS 故障库拿到每台设备在查询范围内的停机分钟数等基础数据。
        //         这里 SQL 只负责捞停机明细，不负责算计划工时，避免 SQL 里写死日历逻辑。
        List<DeviceOeeReport> list = dmsReportMapper.deviceOeeReportList(params);

        // 第二步：根据前端传入的 beginTime/endTime 推出这段时间的"计划工时"（分钟）。
        //         把日历区间计算放在 Java 层，SQL 不用再 care 跨日/跨月/补时分秒。
        long plannedMinutes = calculatePlannedMinutes(params);
        if (plannedMinutes <= 0) {
            // 如果前端没传时间或解析失败，就兜底按一天 24 小时 = 1440 分钟算，
            // 保证下面的 OEE 公式不会出现除零或负数。
            plannedMinutes = 24L * 60L;
        }

        // 第三步：遍历每台设备的报表行，基于停机时间算可用率 (Availability)，
        //         并组装最终的 OEE（OEE = Availability × Performance × Quality）。
        for (DeviceOeeReport item : list) {
            // Mapper 返回里可能有空对象（极少数边缘情况），直接跳过避免 NPE。
            if (item == null) {
                continue;
            }

            // 把本次使用的计划工时回写给 VO，前端可能要展示"本期计划工时"列。
            item.setPLANNED_TIME_MINUTES(plannedMinutes);

            // DOWNTIME_MINUTES 是 SQL 里 SUM 出来的停机分钟，可能全期间没故障 → null。
            // 统一兜底成 0，后续减法才能安全进行。
            Long downtime = item.getDOWNTIME_MINUTES();
            if (downtime == null) {
                downtime = 0L;
            }

            // 可用率 = （计划工时 - 停机分钟）/ 计划工时
            double availability;
            if (plannedMinutes <= 0) {
                // 理论上上面已经兜底过，这里是"双保险"，绝对不让除数为 0。
                availability = 1.0D;
            } else {
                availability = (double) (plannedMinutes - downtime) / (double) plannedMinutes;
                // 处理脏数据：停机时间比计划时间还大 → 截断到 0，防止负可用率。
                if (availability < 0) {
                    availability = 0;
                }
                // 理论不会 >1，但这里是防御性截断，避免浮点舍入误差超过 1。
                if (availability > 1) {
                    availability = 1;
                }
            }

            // 当前版本暂不从生产/质量模块获取性能与良品率，先按 1 处理，可后续扩展
            // —— 性能 (Performance) 需要理论节拍 vs 实际节拍，需要接生产采集；
            // —— 质量 (Quality) 需要合格品/总产量，需要接质检模块。
            // 先给默认 1.0，OEE 数值就退化为可用率，等接通后再接真数据。
            double performance = 1.0D;
            double quality = 1.0D;

            // 统一保留 4 位小数，前端展示时再根据需要转百分比。
            item.setAVAILABILITY(round(availability, 4));
            item.setPERFORMANCE(round(performance, 4));
            item.setQUALITY(round(quality, 4));
            // OEE 是三大指标乘积，任何一个为 0 整体就为 0，业务口径标准。
            item.setOEE(round(availability * performance * quality, 4));
        }
        return list;
    }

    /**
     * 根据前端传入的 beginTime/endTime 计算计划时间（分钟）
     */
    private long calculatePlannedMinutes(Map<String, Object> params) {
        // 参数 Map 为 null 时直接返回 0，外层会走默认 1440 分钟兜底。
        if (params == null) {
            return 0L;
        }

        // 从 Map 里取起止时间；前端 RuoYi 默认会把 beginTime/endTime 塞到 params 里。
        Object beginObj = params.get("beginTime");
        Object endObj = params.get("endTime");
        if (beginObj == null || endObj == null) {
            // 任一端缺失就认为用户没选时间，返回 0 走兜底。
            return 0L;
        }

        // 用 String.valueOf 而不是强转，是因为 Mapper 层 Map 可能塞进 Date / String / Timestamp。
        // 统一转成字符串后再按格式解析，避免 ClassCastException。
        String beginStr = String.valueOf(beginObj);
        String endStr = String.valueOf(endObj);
        if (beginStr.isEmpty() || endStr.isEmpty()) {
            return 0L;
        }

        // 统一补上时间，按自然日计算
        // —— 前端日期选择器可能只给 "yyyy-MM-dd"（10 位），这里把首尾补成整天，
        //    保证"选 2026-04-01 到 2026-04-01"会被算成一整天 1440 分钟，而不是 0。
        if (beginStr.length() == 10) {
            beginStr = beginStr + " 00:00:00";
        }
        if (endStr.length() == 10) {
            endStr = endStr + " 23:59:59";
        }

        // 注意：SimpleDateFormat 非线程安全，所以每次方法调用都 new 一个，不做成常量。
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date begin = sdf.parse(beginStr);
            Date end = sdf.parse(endStr);
            if (begin == null || end == null) {
                // parse 正常情况下不会返回 null，这里只是防御性判断。
                return 0L;
            }

            // 用毫秒差反推分钟数；不使用 Duration/ChronoUnit 是为了兼容老模块里流行的 Date 风格。
            long diffMillis = end.getTime() - begin.getTime();
            if (diffMillis <= 0) {
                // 结束早于开始 → 脏数据，直接返回 0 走兜底，不让后面算出负的可用率。
                return 0L;
            }
            // 毫秒 → 分钟（整除，尾数秒忽略，对 OEE 计算精度影响可以忽略）。
            return diffMillis / (1000L * 60L);
        } catch (ParseException e) {
            // 前端传入的字符串不符合 yyyy-MM-dd HH:mm:ss，不抛异常，返回 0 走兜底，
            // 保证报表至少能出数据，而不是因为一个格式错误整个报表 500。
            return 0L;
        }
    }

    private double round(double value, int scale) {
        // 小数位负数认为不需要四舍五入，直接返回原值。
        if (scale < 0) {
            return value;
        }
        // 通用四舍五入：乘以 10^scale → 四舍五入到整数 → 再除回去。
        // 不使用 BigDecimal 是因为 OEE 计算对精度要求不高，这里追求极简 + 性能。
        double factor = Math.pow(10, scale);
        return Math.round(value * factor) / factor;
    }
}
