package com.zksy.base.service.impl;

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.base.audio.domain.NoiseInfo;
import com.zksy.base.audio.mapper.NoiseInfoMapper;
import com.zksy.base.domain.EquipmentBase;
import com.zksy.base.domain.EquipmentType;
import com.zksy.base.domain.vo.FlowMonitoringVO;
import com.zksy.base.domain.vo.LeakageMonitoringVO;
import com.zksy.base.domain.vo.PressureMonitoringVO;
import com.zksy.base.mapper.EquipmentBaseMapper;
import com.zksy.base.mapper.EquipmentTypeMapper;
import com.zksy.base.pressure.domain.FirefightingPressure;
import com.zksy.base.pressure.mapper.FirefightingPressureMapper;
import com.zksy.base.radar.domain.RadarData;
import com.zksy.base.radar.mapper.RadarDataMapper;
import com.zksy.base.service.MonitoringService;
import com.zksy.base.telemetry.domain.TelemetryData;
import com.zksy.base.telemetry.mapper.TelemetryDataMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class MonitoringServiceImpl implements MonitoringService {

    @Autowired
    private EquipmentBaseMapper equipmentBaseMapper;

    @Autowired
    private EquipmentTypeMapper equipmentTypeMapper;

    @Autowired
    private RadarDataMapper radarDataMapper;

    @Autowired
    private TelemetryDataMapper telemetryDataMapper;

    @Autowired
    private FirefightingPressureMapper firefightingPressureMapper;

    @Autowired
    private NoiseInfoMapper noiseInfoMapper;

    @Override
    public Page<FlowMonitoringVO> getFlowMonitorPage(long pageNum, long pageSize,
                                                      String equipmentName, String equipmentCode,
                                                      String equipmentLocation,
                                                      LocalDateTime startTime, LocalDateTime endTime,
                                                      Double flowMin, Double flowMax) {
        LambdaQueryWrapper<EquipmentBase> baseWrapper = new LambdaQueryWrapper<>();
        baseWrapper.like(StrUtil.isNotBlank(equipmentName), EquipmentBase::getEquipmentName, equipmentName)
                .like(StrUtil.isNotBlank(equipmentCode), EquipmentBase::getEquipmentCode, equipmentCode)
                .like(StrUtil.isNotBlank(equipmentLocation), EquipmentBase::getEquipmentLocation, equipmentLocation)
                .orderByDesc(EquipmentBase::getCreateTime);
        constrainToMonitoringTypes(baseWrapper, "flow");

        Page<EquipmentBase> basePage = equipmentBaseMapper.selectPage(new Page<>(pageNum, pageSize), baseWrapper);
        Page<FlowMonitoringVO> resultPage = new Page<>(pageNum, pageSize, basePage.getTotal());

        if (CollUtil.isEmpty(basePage.getRecords())) {
            resultPage.setRecords(new ArrayList<>());
            return resultPage;
        }

        List<EquipmentBase> devices = basePage.getRecords();
        List<String> deviceCodes = devices.stream()
                .map(EquipmentBase::getEquipmentCode).filter(StrUtil::isNotBlank).collect(Collectors.toList());
        List<String> typeIds = devices.stream()
                .map(EquipmentBase::getEquipmentTypeId).filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList());

        Map<String, EquipmentType> typeMap = new HashMap<>();
        if (CollUtil.isNotEmpty(typeIds)) {
            List<EquipmentType> types = equipmentTypeMapper.selectBatchIds(typeIds);
            typeMap = types.stream().collect(Collectors.toMap(EquipmentType::getTypeId, t -> t, (a, b) -> a));
        }

        Map<String, RadarData> radarLatestMap = new HashMap<>();
        Map<String, TelemetryData> telemetryLatestMap = new HashMap<>();

        if (CollUtil.isNotEmpty(deviceCodes)) {
            List<RadarData> radarDataList = radarDataMapper.selectList(
                    new LambdaQueryWrapper<RadarData>().in(RadarData::getDeviceCode, deviceCodes)
                            .ge(startTime != null, RadarData::getCreateTime, startTime)
                            .le(endTime != null, RadarData::getCreateTime, endTime)
                            .orderByDesc(RadarData::getCreateTime)
            );
            if (CollUtil.isNotEmpty(radarDataList)) {
                Set<String> seen = new HashSet<>();
                for (RadarData d : radarDataList) {
                    if (seen.add(d.getDeviceCode())) {
                        radarLatestMap.put(d.getDeviceCode(), d);
                    }
                }
            }

            List<TelemetryData> telemetryDataList = telemetryDataMapper.selectList(
                    new LambdaQueryWrapper<TelemetryData>().in(TelemetryData::getDeviceCode, deviceCodes)
                            .ge(startTime != null, TelemetryData::getCreateTime, startTime)
                            .le(endTime != null, TelemetryData::getCreateTime, endTime)
                            .orderByDesc(TelemetryData::getCreateTime)
            );
            if (CollUtil.isNotEmpty(telemetryDataList)) {
                Set<String> seen = new HashSet<>();
                for (TelemetryData d : telemetryDataList) {
                    if (seen.add(d.getDeviceCode())) {
                        telemetryLatestMap.put(d.getDeviceCode(), d);
                    }
                }
            }
        }

        List<FlowMonitoringVO> voList = new ArrayList<>();
        for (EquipmentBase device : devices) {
            FlowMonitoringVO vo = new FlowMonitoringVO();
            vo.setEquipmentId(device.getEquipmentId());
            vo.setEquipmentCode(device.getEquipmentCode());
            vo.setEquipmentName(device.getEquipmentName());
            vo.setEquipmentLocation(device.getEquipmentLocation());
            vo.setEquipmentTypeId(device.getEquipmentTypeId());
            EquipmentType type = typeMap.get(device.getEquipmentTypeId());
            if (type != null) {
                vo.setEquipmentTypeName(type.getTypeName());
            }
            vo.setLongitude(device.getLongitude());
            vo.setLatitude(device.getLatitude());
            if (device.getLongitude() != null && device.getLatitude() != null) {
                vo.setGps(device.getLongitude() + "," + device.getLatitude());
            }

            String code = device.getEquipmentCode();
            boolean matched = false;

            RadarData radar = radarLatestMap.get(code);
            if (radar != null && isFlowInRange(radar.getMeter1InstantFlow(), flowMin, flowMax)) {
                vo.setDataSource("雷达流量计");
                vo.setInstantFlow(radar.getMeter1InstantFlow());
                vo.setFlowSpeed(radar.getFlowSpeed());
                vo.setMonitorTime(radar.getCreateTime());
                matched = true;
            }

            if (!matched) {
                TelemetryData telemetry = telemetryLatestMap.get(code);
                if (telemetry != null && isFlowInRange(telemetry.getMeter1PositiveTotal(), flowMin, flowMax)) {
                    vo.setDataSource("遥测终端");
                    vo.setPositiveTotal(telemetry.getMeter1PositiveTotal());
                    vo.setTemperature(telemetry.getTemperature());
                    vo.setFlowSpeed(telemetry.getFlowSpeed());
                    vo.setMonitorTime(telemetry.getCreateTime());
                    matched = true;
                }
            }

            if (matched || (flowMin == null && flowMax == null)) {
                voList.add(vo);
            }
        }

        resultPage.setRecords(voList);
        return resultPage;
    }

    private boolean isFlowInRange(String flowStr, Double min, Double max) {
        if (min == null && max == null) {
            return true;
        }
        if (StrUtil.isBlank(flowStr)) {
            return false;
        }
        try {
            double val = Double.parseDouble(flowStr);
            if (min != null && val < min) {
                return false;
            }
            if (max != null && val > max) {
                return false;
            }
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    @Override
    public Page<PressureMonitoringVO> getPressureMonitorPage(long pageNum, long pageSize,
                                                              String equipmentName, String equipmentCode,
                                                              String equipmentLocation,
                                                              LocalDateTime startTime, LocalDateTime endTime,
                                                              Double pressureMin, Double pressureMax) {
        LambdaQueryWrapper<EquipmentBase> baseWrapper = new LambdaQueryWrapper<>();
        baseWrapper.like(StrUtil.isNotBlank(equipmentName), EquipmentBase::getEquipmentName, equipmentName)
                .like(StrUtil.isNotBlank(equipmentCode), EquipmentBase::getEquipmentCode, equipmentCode)
                .like(StrUtil.isNotBlank(equipmentLocation), EquipmentBase::getEquipmentLocation, equipmentLocation)
                .orderByDesc(EquipmentBase::getCreateTime);
        constrainToMonitoringTypes(baseWrapper, "pressure");

        Page<EquipmentBase> basePage = equipmentBaseMapper.selectPage(new Page<>(pageNum, pageSize), baseWrapper);
        Page<PressureMonitoringVO> resultPage = new Page<>(pageNum, pageSize, basePage.getTotal());

        if (CollUtil.isEmpty(basePage.getRecords())) {
            resultPage.setRecords(new ArrayList<>());
            return resultPage;
        }

        List<EquipmentBase> devices = basePage.getRecords();
        List<String> deviceCodes = devices.stream()
                .map(EquipmentBase::getEquipmentCode).filter(StrUtil::isNotBlank).collect(Collectors.toList());
        List<String> typeIds = devices.stream()
                .map(EquipmentBase::getEquipmentTypeId).filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList());

        Map<String, EquipmentType> typeMap = new HashMap<>();
        if (CollUtil.isNotEmpty(typeIds)) {
            List<EquipmentType> types = equipmentTypeMapper.selectBatchIds(typeIds);
            typeMap = types.stream().collect(Collectors.toMap(EquipmentType::getTypeId, t -> t, (a, b) -> a));
        }

        Map<String, FirefightingPressure> pressureLatestMap = new HashMap<>();
        if (CollUtil.isNotEmpty(deviceCodes)) {
            List<FirefightingPressure> pressureList = firefightingPressureMapper.selectList(
                    new LambdaQueryWrapper<FirefightingPressure>().in(FirefightingPressure::getTelemeteringStation, deviceCodes)
                            .ge(startTime != null, FirefightingPressure::getCreateTime, startTime)
                            .le(endTime != null, FirefightingPressure::getCreateTime, endTime)
                            .ge(pressureMin != null, FirefightingPressure::getPressureValue, pressureMin)
                            .le(pressureMax != null, FirefightingPressure::getPressureValue, pressureMax)
                            .orderByDesc(FirefightingPressure::getCreateTime)
            );
            if (CollUtil.isNotEmpty(pressureList)) {
                Set<String> seen = new HashSet<>();
                for (FirefightingPressure p : pressureList) {
                    if (seen.add(p.getTelemeteringStation())) {
                        pressureLatestMap.put(p.getTelemeteringStation(), p);
                    }
                }
            }
        }

        List<PressureMonitoringVO> voList = new ArrayList<>();
        for (EquipmentBase device : devices) {
            PressureMonitoringVO vo = new PressureMonitoringVO();
            vo.setEquipmentId(device.getEquipmentId());
            vo.setEquipmentCode(device.getEquipmentCode());
            vo.setEquipmentName(device.getEquipmentName());
            vo.setEquipmentLocation(device.getEquipmentLocation());
            vo.setEquipmentTypeId(device.getEquipmentTypeId());
            EquipmentType type = typeMap.get(device.getEquipmentTypeId());
            if (type != null) {
                vo.setEquipmentTypeName(type.getTypeName());
            }
            vo.setLongitude(device.getLongitude());
            vo.setLatitude(device.getLatitude());
            if (device.getLongitude() != null && device.getLatitude() != null) {
                vo.setGps(device.getLongitude() + "," + device.getLatitude());
            }

            FirefightingPressure pressure = pressureLatestMap.get(device.getEquipmentCode());
            if (pressure != null) {
                vo.setPressureValue(pressure.getPressureValue());
                vo.setMonitorTime(pressure.getCreateTime());
            }
            voList.add(vo);
        }

        resultPage.setRecords(voList);
        return resultPage;
    }

    @Override
    public Page<LeakageMonitoringVO> getLeakageMonitorPage(long pageNum, long pageSize,
                                                            String equipmentName, String equipmentCode,
                                                            String equipmentLocation,
                                                            LocalDateTime startTime, LocalDateTime endTime,
                                                            Double powerMin, Double powerMax) {
        LambdaQueryWrapper<EquipmentBase> baseWrapper = new LambdaQueryWrapper<>();
        baseWrapper.like(StrUtil.isNotBlank(equipmentName), EquipmentBase::getEquipmentName, equipmentName)
                .like(StrUtil.isNotBlank(equipmentCode), EquipmentBase::getEquipmentCode, equipmentCode)
                .like(StrUtil.isNotBlank(equipmentLocation), EquipmentBase::getEquipmentLocation, equipmentLocation)
                .orderByDesc(EquipmentBase::getCreateTime);
        constrainToMonitoringTypes(baseWrapper, "leakage");

        Page<EquipmentBase> basePage = equipmentBaseMapper.selectPage(new Page<>(pageNum, pageSize), baseWrapper);
        Page<LeakageMonitoringVO> resultPage = new Page<>(pageNum, pageSize, basePage.getTotal());

        if (CollUtil.isEmpty(basePage.getRecords())) {
            resultPage.setRecords(new ArrayList<>());
            return resultPage;
        }

        List<EquipmentBase> devices = basePage.getRecords();
        List<String> deviceCodes = devices.stream()
                .map(EquipmentBase::getEquipmentCode).filter(StrUtil::isNotBlank).collect(Collectors.toList());
        List<String> typeIds = devices.stream()
                .map(EquipmentBase::getEquipmentTypeId).filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList());

        Map<String, EquipmentType> typeMap = new HashMap<>();
        if (CollUtil.isNotEmpty(typeIds)) {
            List<EquipmentType> types = equipmentTypeMapper.selectBatchIds(typeIds);
            typeMap = types.stream().collect(Collectors.toMap(EquipmentType::getTypeId, t -> t, (a, b) -> a));
        }

        Map<String, NoiseInfo> noiseLatestMap = new HashMap<>();
        if (CollUtil.isNotEmpty(deviceCodes)) {
            List<NoiseInfo> noiseList = noiseInfoMapper.selectList(
                    new LambdaQueryWrapper<NoiseInfo>().in(NoiseInfo::getEncode, deviceCodes)
                            .ge(startTime != null, NoiseInfo::getCreateTime, startTime)
                            .le(endTime != null, NoiseInfo::getCreateTime, endTime)
                            .ge(powerMin != null, NoiseInfo::getPower, powerMin)
                            .le(powerMax != null, NoiseInfo::getPower, powerMax)
                            .orderByDesc(NoiseInfo::getCreateTime)
            );
            if (CollUtil.isNotEmpty(noiseList)) {
                Set<String> seen = new HashSet<>();
                for (NoiseInfo n : noiseList) {
                    if (seen.add(n.getEncode())) {
                        noiseLatestMap.put(n.getEncode(), n);
                    }
                }
            }
        }

        List<LeakageMonitoringVO> voList = new ArrayList<>();
        for (EquipmentBase device : devices) {
            LeakageMonitoringVO vo = new LeakageMonitoringVO();
            vo.setEquipmentId(device.getEquipmentId());
            vo.setEquipmentCode(device.getEquipmentCode());
            vo.setEquipmentName(device.getEquipmentName());
            vo.setEquipmentLocation(device.getEquipmentLocation());
            vo.setEquipmentTypeId(device.getEquipmentTypeId());
            EquipmentType type = typeMap.get(device.getEquipmentTypeId());
            if (type != null) {
                vo.setEquipmentTypeName(type.getTypeName());
            }
            vo.setLongitude(device.getLongitude());
            vo.setLatitude(device.getLatitude());
            if (device.getLongitude() != null && device.getLatitude() != null) {
                vo.setGps(device.getLongitude() + "," + device.getLatitude());
            }

            NoiseInfo noise = noiseLatestMap.get(device.getEquipmentCode());
            if (noise != null) {
                vo.setPower(noise.getPower());
                vo.setGain(noise.getGain());
                vo.setRms(noise.getRms());
                vo.setCenterFrequency(noise.getCenterFrequency());
                vo.setMonitorTime(noise.getCreateTime());
            }
            voList.add(vo);
        }

        resultPage.setRecords(voList);
        return resultPage;
    }

    @Override
    public List<Map<String, Object>> getFlowTrend24h(String equipmentId) {
        EquipmentBase device = equipmentBaseMapper.selectById(equipmentId);
        if (device == null || StrUtil.isBlank(device.getEquipmentCode())) {
            return new ArrayList<>();
        }

        LocalDateTime now = LocalDateTime.now();
        LocalDateTime before24h = now.minusHours(24);
        String code = device.getEquipmentCode();

        List<Map<String, Object>> result = new ArrayList<>();

        List<RadarData> radarList = radarDataMapper.selectList(
                new LambdaQueryWrapper<RadarData>().eq(RadarData::getDeviceCode, code)
                        .ge(RadarData::getCreateTime, before24h)
                        .orderByAsc(RadarData::getCreateTime)
        );
        if (CollUtil.isNotEmpty(radarList)) {
            for (RadarData d : radarList) {
                Map<String, Object> point = new LinkedHashMap<>();
                point.put("time", d.getCreateTime());
                point.put("instantFlow", d.getMeter1InstantFlow());
                point.put("flowSpeed", d.getFlowSpeed());
                result.add(point);
            }
            return result;
        }

        List<TelemetryData> telemetryList = telemetryDataMapper.selectList(
                new LambdaQueryWrapper<TelemetryData>().eq(TelemetryData::getDeviceCode, code)
                        .ge(TelemetryData::getCreateTime, before24h)
                        .orderByAsc(TelemetryData::getCreateTime)
        );
        for (TelemetryData d : telemetryList) {
            Map<String, Object> point = new LinkedHashMap<>();
            point.put("time", d.getCreateTime());
            point.put("positiveTotal", d.getMeter1PositiveTotal());
            point.put("temperature", d.getTemperature());
            point.put("flowSpeed", d.getFlowSpeed());
            result.add(point);
        }
        return result;
    }

    @Override
    public List<Map<String, Object>> getPressureTrend24h(String equipmentId) {
        EquipmentBase device = equipmentBaseMapper.selectById(equipmentId);
        if (device == null || StrUtil.isBlank(device.getEquipmentCode())) {
            return new ArrayList<>();
        }

        LocalDateTime now = LocalDateTime.now();
        LocalDateTime before24h = now.minusHours(24);

        List<FirefightingPressure> list = firefightingPressureMapper.selectList(
                new LambdaQueryWrapper<FirefightingPressure>().eq(FirefightingPressure::getTelemeteringStation, device.getEquipmentCode())
                        .ge(FirefightingPressure::getCreateTime, before24h)
                        .orderByAsc(FirefightingPressure::getCreateTime)
        );

        List<Map<String, Object>> result = new ArrayList<>();
        for (FirefightingPressure p : list) {
            Map<String, Object> point = new LinkedHashMap<>();
            point.put("time", p.getCreateTime());
            point.put("pressureValue", p.getPressureValue());
            result.add(point);
        }
        return result;
    }

    @Override
    public List<Map<String, Object>> getLeakageTrend24h(String equipmentId) {
        EquipmentBase device = equipmentBaseMapper.selectById(equipmentId);
        if (device == null || StrUtil.isBlank(device.getEquipmentCode())) {
            return new ArrayList<>();
        }

        LocalDateTime now = LocalDateTime.now();
        LocalDateTime before24h = now.minusHours(24);

        List<NoiseInfo> list = noiseInfoMapper.selectList(
                new LambdaQueryWrapper<NoiseInfo>().eq(NoiseInfo::getEncode, device.getEquipmentCode())
                        .ge(NoiseInfo::getCreateTime, before24h)
                        .orderByAsc(NoiseInfo::getCreateTime)
        );

        List<Map<String, Object>> result = new ArrayList<>();
        for (NoiseInfo n : list) {
            Map<String, Object> point = new LinkedHashMap<>();
            point.put("time", n.getCreateTime());
            point.put("power", n.getPower());
            point.put("gain", n.getGain());
            point.put("rms", n.getRms());
            result.add(point);
        }
        return result;
    }

    /** Restrict the equipment query before pagination so each monitoring page only contains its sub-devices. */
    private void constrainToMonitoringTypes(LambdaQueryWrapper<EquipmentBase> wrapper, String monitorKind) {
        String[] keywords;
        switch (monitorKind) {
            case "pressure":
                keywords = new String[]{"压力", "pressure", "消防"};
                break;
            case "leakage":
                keywords = new String[]{"漏失", "漏损", "泄漏", "噪声", "音频", "noise"};
                break;
            default:
                keywords = new String[]{"流量", "flow", "雷达", "遥测", "telemetry"};
                break;
        }
        List<EquipmentType> types = equipmentTypeMapper.selectList(new LambdaQueryWrapper<EquipmentType>());
        Set<String> ids = types.stream()
                .filter(type -> {
                    String value = ((type.getTypeId() == null ? "" : type.getTypeId()) + " "
                            + (type.getTypeName() == null ? "" : type.getTypeName())).toLowerCase(Locale.ROOT);
                    return Arrays.stream(keywords).anyMatch(keyword -> value.contains(keyword.toLowerCase(Locale.ROOT)));
                })
                .map(EquipmentType::getId)
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toSet());
        // Keep an empty result portable across database drivers that reject IN ().
        wrapper.in(EquipmentBase::getEquipmentTypeId,
                ids.isEmpty() ? Collections.singleton("__NO_MONITORING_TYPE__") : ids);
    }
}
