package com.zksy.WaterSupply.MonitorAlarm.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zksy.WaterSupply.MonitorAlarm.domain.AlarmAudit;
import com.zksy.WaterSupply.MonitorAlarm.dto.in.AlarmAuditInDTO;
import com.zksy.WaterSupply.MonitorAlarm.dto.in.AlarmDispatchInDTO;
import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmAuditOutDTO;
import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmFlowOutDTO;
import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmMapPointOutDTO;
import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmStatsOutDTO;
import com.zksy.WaterSupply.MonitorAlarm.dto.out.WaterAlarmOutDTO;
import com.zksy.WaterSupply.MonitorAlarm.mapper.AlarmAuditMapper;
import com.zksy.WaterSupply.MonitorAlarm.service.IWaterSupplyAlarmService;
import com.zksy.base.alarm.domain.AlarmData;
import com.zksy.base.alarm.service.AlarmDataService;
import com.zksy.base.domain.EquipmentBase;
import com.zksy.base.domain.WorkOrder;
import com.zksy.base.domain.WorkOrderLog;
import com.zksy.base.mapper.EquipmentBaseMapper;
import com.zksy.base.mapper.WorkOrderLogMapper;
import com.zksy.base.mapper.WorkOrderMapper;
import com.zksy.base.service.EquipmentBaseService;
import com.zksy.common.core.domain.model.LoginUser;
import com.zksy.common.exception.ServiceException;
import com.zksy.common.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 供水监测报警业务实现
 */
@Slf4j
@Service
public class WaterSupplyAlarmServiceImpl implements IWaterSupplyAlarmService {

    /**
     * 供水设备顶级类型名称（equipment_type 中 parent_type_id='0' 的顶级类型）
     */
    @Value("${waterSupply.alarm.top-level-type:供水}")
    private String waterTopLevelTypeName;

    private static final DateTimeFormatter ORDER_NO_FMT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    @Autowired
    private AlarmDataService alarmDataService;

    @Autowired
    private EquipmentBaseService equipmentBaseService;

    @Autowired
    private EquipmentBaseMapper equipmentBaseMapper;

    @Autowired
    private WorkOrderMapper workOrderMapper;

    @Autowired
    private WorkOrderLogMapper workOrderLogMapper;

    @Autowired
    private AlarmAuditMapper alarmAuditMapper;

    // ------------------------------------------------------------------
    // 供水设备范围解析
    // ------------------------------------------------------------------

    /**
     * 解析全部供水设备编码（按设备类别顶级类型"供水"动态查询）
     */
    private Set<String> resolveWaterDeviceCodes() {
        return equipmentBaseService.findByTopLevelType(waterTopLevelTypeName).stream()
                .map(EquipmentBase::getEquipmentCode)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
    }

    /**
     * 设备编码 -> 设备信息映射
     */
    private Map<String, EquipmentBase> loadEquipmentCodeMap() {
        return equipmentBaseMapper.selectList(null).stream()
                .collect(Collectors.toMap(EquipmentBase::getEquipmentCode, Function.identity(), (a, b) -> a));
    }

    /**
     * 设备编码 -> 工单映射（同一报警派发多个工单时取最新）
     */
    private Map<String, WorkOrder> loadWorkOrderMapByAlarmIds(List<String> alarmIds) {
        if (CollUtil.isEmpty(alarmIds)) {
            return Collections.emptyMap();
        }
        List<WorkOrder> orders = workOrderMapper.selectList(new LambdaQueryWrapper<WorkOrder>()
                .in(WorkOrder::getAlarmId, alarmIds)
                .orderByDesc(WorkOrder::getOrderId));
        return orders.stream()
                .collect(Collectors.toMap(WorkOrder::getAlarmId, Function.identity(), (a, b) -> a));
    }

    // ------------------------------------------------------------------
    // 统计
    // ------------------------------------------------------------------

    @Override
    public AlarmStatsOutDTO getAlarmStats() {
        AlarmStatsOutDTO stats = new AlarmStatsOutDTO();
        Set<String> waterCodes = resolveWaterDeviceCodes();
        stats.setTotalDeviceCount(waterCodes.size());
        if (waterCodes.isEmpty()) {
            stats.setDeviceAbnormalRate("0.00");
            stats.setAvgResponseMinutes("0.00");
            return stats;
        }

        // 未处理报警（实时报警）
        List<AlarmData> pendingAlarms = alarmDataService.list(new LambdaQueryWrapper<AlarmData>()
                .in(AlarmData::getDeviceCode, waterCodes)
                .eq(AlarmData::getAlarmStatus, 0));
        Set<String> abnormalDevices = pendingAlarms.stream()
                .map(AlarmData::getDeviceCode)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        stats.setRealtimeAlarmCount(abnormalDevices.size());
        stats.setAbnormalDeviceCount(abnormalDevices.size());
        stats.setDeviceAbnormalRate(waterCodes.isEmpty() ? "0.00"
                : String.format("%.2f", abnormalDevices.size() * 100.0d / waterCodes.size()));

        // 今日报警总数
        LocalDateTime todayStart = LocalDate.now().atStartOfDay();
        long todayCount = alarmDataService.count(new LambdaQueryWrapper<AlarmData>()
                .in(AlarmData::getDeviceCode, waterCodes)
                .ge(AlarmData::getAlarmTime, todayStart));
        stats.setTodayAlarmCount(todayCount);

        // 平均响应时间：今日已处理报警（处理时间-报警时间）均值，单位分钟
        List<AlarmData> handledToday = alarmDataService.list(new LambdaQueryWrapper<AlarmData>()
                .in(AlarmData::getDeviceCode, waterCodes)
                .eq(AlarmData::getAlarmStatus, 1)
                .ge(AlarmData::getHandleTime, todayStart));
        double totalSeconds = 0;
        int count = 0;
        for (AlarmData alarm : handledToday) {
            if (alarm.getAlarmTime() == null || alarm.getHandleTime() == null) {
                continue;
            }
            totalSeconds += alarm.getHandleTime().toEpochSecond(java.time.ZoneOffset.ofHours(8))
                    - alarm.getAlarmTime().toEpochSecond(java.time.ZoneOffset.ofHours(8));
            count++;
        }
        stats.setAvgResponseMinutes(count == 0 ? "0.00" : String.format("%.2f", totalSeconds / 60.0d / count));
        return stats;
    }

    // ------------------------------------------------------------------
    // 实时 / 历史 / 详情
    // ------------------------------------------------------------------

    @Override
    public List<WaterAlarmOutDTO> getRealtimeAlarmList() {
        Set<String> waterCodes = resolveWaterDeviceCodes();
        if (waterCodes.isEmpty()) {
            return new ArrayList<>();
        }
        List<AlarmData> alarms = alarmDataService.list(new LambdaQueryWrapper<AlarmData>()
                .in(AlarmData::getDeviceCode, waterCodes)
                .eq(AlarmData::getAlarmStatus, 0)
                .orderByDesc(AlarmData::getAlarmTime));
        return convertToVoList(alarms);
    }

    @Override
    public Page<WaterAlarmOutDTO> getHistoryAlarmPage(long pageNum, long pageSize,
                                                      String deviceCode, String warningType,
                                                      Integer alarmLevel, Integer alarmStatus,
                                                      LocalDateTime startTime, LocalDateTime endTime,
                                                      String keyword) {
        Page<AlarmData> page = new Page<>(pageNum, pageSize);
        Set<String> waterCodes = resolveWaterDeviceCodes();
        LambdaQueryWrapper<AlarmData> wrapper = new LambdaQueryWrapper<>();
        if (!waterCodes.isEmpty()) {
            wrapper.in(AlarmData::getDeviceCode, waterCodes);
        } else {
            wrapper.eq(AlarmData::getId, "-1");
        }
        wrapper.eq(StrUtil.isNotBlank(deviceCode), AlarmData::getDeviceCode, deviceCode)
                .like(StrUtil.isNotBlank(warningType), AlarmData::getWarningType, warningType)
                .eq(alarmLevel != null, AlarmData::getAlarmLevel, alarmLevel)
                .eq(alarmStatus != null, AlarmData::getAlarmStatus, alarmStatus)
                .ge(startTime != null, AlarmData::getAlarmTime, startTime)
                .le(endTime != null, AlarmData::getAlarmTime, endTime)
                .and(StrUtil.isNotBlank(keyword), w -> w.like(AlarmData::getDeviceCode, keyword)
                        .or().like(AlarmData::getWarningType, keyword)
                        .or().like(AlarmData::getWarningCode, keyword))
                .orderByDesc(AlarmData::getAlarmTime);
        Page<AlarmData> result = alarmDataService.page(page, wrapper);
        Page<WaterAlarmOutDTO> outPage = new Page<>(pageNum, pageSize, result.getTotal());
        outPage.setRecords(convertToVoList(result.getRecords()));
        return outPage;
    }

    @Override
    public WaterAlarmOutDTO getAlarmDetail(String alarmId) {
        AlarmData alarm = alarmDataService.getById(alarmId);
        if (alarm == null) {
            throw new ServiceException("报警信息不存在");
        }
        List<WaterAlarmOutDTO> vos = convertToVoList(Collections.singletonList(alarm));
        return vos.isEmpty() ? null : vos.get(0);
    }

    /**
     * 报警记录 -> 出参（填充设备名称/位置/经纬度及关联工单）
     */
    private List<WaterAlarmOutDTO> convertToVoList(List<AlarmData> alarms) {
        if (CollUtil.isEmpty(alarms)) {
            return new ArrayList<>();
        }
        Map<String, EquipmentBase> eqMap = loadEquipmentCodeMap();
        List<String> alarmIds = alarms.stream().map(AlarmData::getId).collect(Collectors.toList());
        Map<String, WorkOrder> orderMap = loadWorkOrderMapByAlarmIds(alarmIds);
        List<WaterAlarmOutDTO> vos = new ArrayList<>();
        for (AlarmData alarm : alarms) {
            WaterAlarmOutDTO vo = BeanUtil.copyProperties(alarm, WaterAlarmOutDTO.class);
            EquipmentBase eq = alarm.getDeviceCode() == null ? null : eqMap.get(alarm.getDeviceCode());
            if (eq != null) {
                vo.setEquipmentId(eq.getEquipmentId());
                if (StrUtil.isBlank(vo.getDeviceName())) {
                    vo.setDeviceName(eq.getEquipmentName());
                }
                vo.setEquipmentLocation(eq.getEquipmentLocation());
                if (eq.getLongitude() != null) {
                    vo.setLongitude(eq.getLongitude().toPlainString());
                }
                if (eq.getLatitude() != null) {
                    vo.setLatitude(eq.getLatitude().toPlainString());
                }
            }
            vo.setAlarmLevelText(formatAlarmLevel(alarm.getAlarmLevel()));
            vo.setAlarmStatusText(alarm.getAlarmStatus() != null && alarm.getAlarmStatus() == 0 ? "待处理" : "已处理");
            WorkOrder order = alarm.getId() == null ? null : orderMap.get(alarm.getId());
            if (order != null) {
                vo.setOrderNo(order.getOrderNo());
                vo.setOrderStatus(order.getOrderStatus());
            }
            vos.add(vo);
        }
        return vos;
    }

    private String formatAlarmLevel(Integer alarmLevel) {
        if (alarmLevel == null) {
            return "";
        }
        switch (alarmLevel) {
            case 1:
                return "一级报警";
            case 2:
                return "二级报警";
            case 3:
                return "三级报警";
            case 4:
                return "四级报警";
            default:
                return "报警";
        }
    }

    // ------------------------------------------------------------------
    // 处理 / 派发工单
    // ------------------------------------------------------------------

    @Override
    public boolean resolveAlarm(String alarmId, String handleRemark) {
        AlarmData alarm = alarmDataService.getById(alarmId);
        if (alarm == null) {
            throw new ServiceException("报警信息不存在");
        }
        alarm.setAlarmStatus(1);
        alarm.setHandleTime(LocalDateTime.now());
        alarm.setHandleUser(currentUserName());
        alarm.setHandleRemark(StrUtil.blankToDefault(handleRemark, "标记已处理"));
        alarm.setUpdateTime(LocalDateTime.now());
        return alarmDataService.updateById(alarm);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean dispatchToWorkOrder(AlarmDispatchInDTO inDTO) {
        if (inDTO == null || StrUtil.isBlank(inDTO.getAlarmId())) {
            throw new ServiceException("报警ID不能为空");
        }
        AlarmData alarm = alarmDataService.getById(inDTO.getAlarmId());
        if (alarm == null) {
            throw new ServiceException("报警信息不存在");
        }

        // 设备信息（用于回填 deviceId/deviceCode）
        EquipmentBase equipment = alarm.getDeviceCode() == null ? null
                : equipmentBaseMapper.selectOne(new LambdaQueryWrapper<EquipmentBase>()
                        .eq(EquipmentBase::getEquipmentCode, alarm.getDeviceCode())
                        .last("LIMIT 1"));

        WorkOrder workOrder = new WorkOrder();
        workOrder.setOrderNo(generateOrderNo());
        workOrder.setAlarmId(alarm.getId());
        workOrder.setDeviceId(equipment != null ? equipment.getEquipmentId() : alarm.getDeviceCode());
        workOrder.setDeviceCode(alarm.getDeviceCode());
        workOrder.setOrderType(inDTO.getOrderType() == null ? 1 : inDTO.getOrderType());
        workOrder.setOrderLevel(inDTO.getOrderLevel() == null
                ? mapAlarmLevelToOrderLevel(alarm.getAlarmLevel()) : inDTO.getOrderLevel());
        workOrder.setOrderDesc(StrUtil.isNotBlank(inDTO.getOrderDesc()) ? inDTO.getOrderDesc()
                : buildDefaultOrderDesc(alarm));
        workOrder.setDeptId(inDTO.getDeptId());
        workOrder.setPlanFinishTime(inDTO.getPlanFinishTime());
        workOrder.setOrderStatus(1); // 1-待派单
        workOrder.setCreateTime(new java.util.Date());
        workOrder.setUpdateTime(new java.util.Date());
        workOrderMapper.insert(workOrder);

        // 写工单操作日志（1-创建工单）
        WorkOrderLog workOrderLog = new WorkOrderLog();
        workOrderLog.setOrderId(workOrder.getOrderId());
        workOrderLog.setOperType(1);
        workOrderLog.setOperDesc("由供水监测报警[" + alarm.getWarningType() + "]派发工单：" + workOrder.getOrderNo());
        workOrderLog.setOperUserId(currentUserId());
        workOrderLog.setOperUserName(currentUserName());
        workOrderLog.setOperTime(new java.util.Date());
        workOrderLogMapper.insert(workOrderLog);

        // 回写报警处理信息
        alarm.setAlarmStatus(1);
        alarm.setHandleTime(LocalDateTime.now());
        alarm.setHandleUser(currentUserName());
        alarm.setHandleRemark("已派发工单：" + workOrder.getOrderNo());
        alarm.setUpdateTime(LocalDateTime.now());
        alarmDataService.updateById(alarm);
        return true;
    }

    /**
     * 报警等级 -> 工单优先级：1级→紧急，2/3级→一般，4级→低
     */
    private Integer mapAlarmLevelToOrderLevel(Integer alarmLevel) {
        if (alarmLevel == null || alarmLevel == 1) {
            return 1;
        }
        if (alarmLevel == 4) {
            return 3;
        }
        return 2;
    }

    private String buildDefaultOrderDesc(AlarmData alarm) {
        StringBuilder sb = new StringBuilder();
        sb.append(StrUtil.blankToDefault(alarm.getWarningType(), "监测报警"));
        sb.append("，当前值：").append(alarm.getActualValue() == null ? "-" : alarm.getActualValue().toPlainString());
        if (alarm.getMinValue() != null || alarm.getMaxValue() != null) {
            sb.append("，阈值范围：").append(alarm.getMinValue() == null ? "-" : alarm.getMinValue().toPlainString())
                    .append("~").append(alarm.getMaxValue() == null ? "-" : alarm.getMaxValue().toPlainString());
        }
        sb.append("，请及时现场核实处理");
        return sb.toString();
    }

    /**
     * 生成工单编号：WO + yyyyMMddHHmmss + 4位随机数
     */
    private String generateOrderNo() {
        return "WO" + LocalDateTime.now().format(ORDER_NO_FMT)
                + String.format("%04d", new Random().nextInt(10000));
    }

    // ------------------------------------------------------------------
    // 报警审核 / 全流程跟踪
    // ------------------------------------------------------------------

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AlarmAuditOutDTO auditAlarm(AlarmAuditInDTO inDTO) {
        if (inDTO == null || StrUtil.isBlank(inDTO.getAlarmId())) {
            throw new ServiceException("报警ID不能为空");
        }
        if (inDTO.getAuditAction() == null) {
            throw new ServiceException("审核动作不能为空");
        }
        if (inDTO.getAuditResult() == null) {
            throw new ServiceException("审核结果不能为空");
        }
        AlarmData alarm = alarmDataService.getById(inDTO.getAlarmId());
        if (alarm == null) {
            throw new ServiceException("报警信息不存在");
        }

        AlarmAudit audit = new AlarmAudit();
        audit.setAlarmId(inDTO.getAlarmId());
        audit.setAuditAction(inDTO.getAuditAction());
        audit.setAuditResult(inDTO.getAuditResult());
        audit.setAuditOpinion(inDTO.getAuditOpinion());
        audit.setAttachUrl(inDTO.getAttachUrl());
        audit.setAuditUserId(currentUserId());
        audit.setAuditUserName(currentUserName());
        audit.setAuditTime(LocalDateTime.now());
        audit.setCreateTime(LocalDateTime.now());
        audit.setUpdateTime(LocalDateTime.now());
        alarmAuditMapper.insert(audit);

        // 审核结果=误报 或 动作=解除 时，直接关闭报警
        boolean releaseAlarm = inDTO.getAuditResult() == 2 || inDTO.getAuditAction() == 2;
        if (releaseAlarm) {
            alarm.setAlarmStatus(1);
            alarm.setHandleTime(LocalDateTime.now());
            alarm.setHandleUser(audit.getAuditUserName());
            alarm.setHandleRemark("误报解除：" + StrUtil.blankToDefault(inDTO.getAuditOpinion(), "经审核确认为误报"));
            alarm.setUpdateTime(LocalDateTime.now());
            alarmDataService.updateById(alarm);
        }
        return toAuditOutDTO(audit);
    }

    @Override
    public List<AlarmAuditOutDTO> getAuditList(String alarmId) {
        List<AlarmAudit> audits = alarmAuditMapper.selectList(new LambdaQueryWrapper<AlarmAudit>()
                .eq(AlarmAudit::getAlarmId, alarmId)
                .orderByDesc(AlarmAudit::getAuditTime));
        return audits.stream().map(this::toAuditOutDTO).collect(Collectors.toList());
    }

    @Override
    public AlarmFlowOutDTO getAlarmFlow(String alarmId) {
        AlarmFlowOutDTO flow = new AlarmFlowOutDTO();
        flow.setAlarm(getAlarmDetail(alarmId));
        flow.setAuditList(getAuditList(alarmId));
        WorkOrder order = workOrderMapper.selectOne(new LambdaQueryWrapper<WorkOrder>()
                .eq(WorkOrder::getAlarmId, alarmId)
                .orderByDesc(WorkOrder::getOrderId)
                .last("LIMIT 1"));
        flow.setWorkOrder(order);
        if (order != null) {
            flow.setWorkOrderLogList(workOrderLogMapper.selectList(new LambdaQueryWrapper<WorkOrderLog>()
                    .eq(WorkOrderLog::getOrderId, order.getOrderId())
                    .orderByAsc(WorkOrderLog::getOperTime)));
        }
        return flow;
    }

    private AlarmAuditOutDTO toAuditOutDTO(AlarmAudit audit) {
        AlarmAuditOutDTO vo = BeanUtil.copyProperties(audit, AlarmAuditOutDTO.class);
        vo.setAuditActionText(audit.getAuditAction() != null && audit.getAuditAction() == 2 ? "解除" : "审核");
        vo.setAuditResultText(audit.getAuditResult() != null && audit.getAuditResult() == 2 ? "误报" : "属实");
        return vo;
    }

    // ------------------------------------------------------------------
    // 监测报警一张图
    // ------------------------------------------------------------------

    @Override
    public List<AlarmMapPointOutDTO> getAlarmMapPoints() {
        Set<String> waterCodes = resolveWaterDeviceCodes();
        if (waterCodes.isEmpty()) {
            return new ArrayList<>();
        }
        List<AlarmData> pendingAlarms = alarmDataService.list(new LambdaQueryWrapper<AlarmData>()
                .in(AlarmData::getDeviceCode, waterCodes)
                .eq(AlarmData::getAlarmStatus, 0)
                .orderByDesc(AlarmData::getAlarmTime));
        Map<String, EquipmentBase> eqMap = loadEquipmentCodeMap();
        // 同一设备只展示最新一条未处理报警
        Map<String, AlarmData> latestPerDevice = new LinkedHashMap<>();
        for (AlarmData alarm : pendingAlarms) {
            latestPerDevice.putIfAbsent(alarm.getDeviceCode(), alarm);
        }
        List<AlarmMapPointOutDTO> points = new ArrayList<>();
        for (AlarmData alarm : latestPerDevice.values()) {
            EquipmentBase eq = alarm.getDeviceCode() == null ? null : eqMap.get(alarm.getDeviceCode());
            if (eq == null || eq.getLongitude() == null || eq.getLatitude() == null) {
                continue;
            }
            AlarmMapPointOutDTO point = new AlarmMapPointOutDTO();
            point.setAlarmId(alarm.getId());
            point.setDeviceCode(alarm.getDeviceCode());
            point.setDeviceName(eq.getEquipmentName());
            point.setEquipmentLocation(eq.getEquipmentLocation());
            point.setLongitude(eq.getLongitude().toPlainString());
            point.setLatitude(eq.getLatitude().toPlainString());
            point.setWarningType(alarm.getWarningType());
            point.setActualValue(alarm.getActualValue());
            point.setAlarmLevel(alarm.getAlarmLevel());
            point.setAlarmLevelText(formatAlarmLevel(alarm.getAlarmLevel()));
            point.setAlarmTime(alarm.getAlarmTime());
            points.add(point);
        }
        points.sort(Comparator.comparing(AlarmMapPointOutDTO::getAlarmLevel, Comparator.nullsLast(Comparator.naturalOrder())));
        return points;
    }

    // ------------------------------------------------------------------
    // 当前用户
    // ------------------------------------------------------------------

    private Long currentUserId() {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        return loginUser != null ? loginUser.getUserId() : null;
    }

    private String currentUserName() {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null) {
            return "系统";
        }
        if (loginUser.getUser() != null && StrUtil.isNotBlank(loginUser.getUser().getNickName())) {
            return loginUser.getUser().getNickName();
        }
        return loginUser.getUsername();
    }
}
