package com.zksy.WaterSupply.MonitorAlarm.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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zksy.WaterSupply.MonitorAlarm.dto.in.WaterThresholdSaveInDTO;
import com.zksy.WaterSupply.MonitorAlarm.service.IWaterSupplyThresholdService;
import com.zksy.base.alarm.domain.WarningThreshold;
import com.zksy.base.alarm.mapper.WarningThresholdMapper;
import com.zksy.common.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 供水监测报警阈值业务实现
 */
@Slf4j
@Service
public class WaterSupplyThresholdServiceImpl extends ServiceImpl<WarningThresholdMapper, WarningThreshold>
        implements IWaterSupplyThresholdService {

    private static final Pattern TIME_PATTERN = Pattern.compile("^([01]\\d|2[0-3]):[0-5]\\d$");

    @Override
    public Page<WarningThreshold> findThresholdPage(long pageNum, long pageSize,
                                                    String deviceCode, String warningType,
                                                    String warningCode, String keyword) {
        Page<WarningThreshold> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<WarningThreshold> wrapper = buildQueryWrapper(deviceCode, warningType, warningCode, keyword);
        wrapper.orderByDesc(WarningThreshold::getUpdateTime);
        return this.page(page, wrapper);
    }

    @Override
    public List<WarningThreshold> findThresholdList(String deviceCode, String warningType, String warningCode) {
        LambdaQueryWrapper<WarningThreshold> wrapper = buildQueryWrapper(deviceCode, warningType, warningCode, null);
        wrapper.orderByDesc(WarningThreshold::getUpdateTime);
        return this.list(wrapper);
    }

    @Override
    public WarningThreshold getThresholdDetail(String id) {
        WarningThreshold threshold = this.getById(id);
        if (threshold == null) {
            throw new ServiceException("阈值信息不存在");
        }
        return threshold;
    }

    private LambdaQueryWrapper<WarningThreshold> buildQueryWrapper(String deviceCode, String warningType,
                                                                   String warningCode, String keyword) {
        LambdaQueryWrapper<WarningThreshold> wrapper = new LambdaQueryWrapper<>();
        if (StrUtil.isNotBlank(deviceCode)) {
            // device_code 支持多个设备编号逗号分隔
            wrapper.apply("CONCAT(',', device_code, ',') LIKE CONCAT('%,', {0}, ',%')", deviceCode.trim());
        }
        wrapper.like(StrUtil.isNotBlank(warningType), WarningThreshold::getWarningType, warningType)
                .like(StrUtil.isNotBlank(warningCode), WarningThreshold::getWarningCode, warningCode);
        if (StrUtil.isNotBlank(keyword)) {
            wrapper.and(w -> w.like(WarningThreshold::getDeviceCode, keyword)
                    .or().like(WarningThreshold::getWarningType, keyword)
                    .or().like(WarningThreshold::getWarningCode, keyword));
        }
        return wrapper;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int saveThresholdBatch(WaterThresholdSaveInDTO inDTO) {
        if (inDTO == null || CollUtil.isEmpty(inDTO.getDeviceCodes())) {
            throw new ServiceException("设备编码不能为空");
        }
        if (StrUtil.isBlank(inDTO.getWarningType()) || StrUtil.isBlank(inDTO.getWarningCode())) {
            throw new ServiceException("预警类型和预警编码不能为空");
        }
        if (inDTO.getMinValue() == null && inDTO.getMaxValue() == null) {
            throw new ServiceException("阈值最小值/最大值至少填写一项");
        }
        validatePeriod(inDTO.getPeriodStart(), inDTO.getPeriodEnd());

        int count = 0;
        List<String> deviceCodes = inDTO.getDeviceCodes().stream()
                .filter(StrUtil::isNotBlank)
                .map(String::trim)
                .distinct()
                .collect(Collectors.toList());
        for (String deviceCode : deviceCodes) {
            checkPeriodConflict(deviceCode, inDTO.getWarningCode(),
                    inDTO.getPeriodStart(), inDTO.getPeriodEnd(), null);
            WarningThreshold threshold = new WarningThreshold();
            threshold.setDeviceCode(deviceCode);
            threshold.setWarningType(inDTO.getWarningType().trim());
            threshold.setWarningCode(inDTO.getWarningCode().trim());
            threshold.setMinValue(inDTO.getMinValue());
            threshold.setMaxValue(inDTO.getMaxValue());
            threshold.setPeriodStart(inDTO.getPeriodStart());
            threshold.setPeriodEnd(inDTO.getPeriodEnd());
            threshold.setRemark(inDTO.getRemark());
            threshold.setCreateTime(LocalDateTime.now());
            threshold.setUpdateTime(LocalDateTime.now());
            this.save(threshold);
            count++;
        }
        return count;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateThreshold(WarningThreshold threshold) {
        if (threshold == null || StrUtil.isBlank(threshold.getId())) {
            throw new ServiceException("阈值ID不能为空");
        }
        WarningThreshold exist = this.getById(threshold.getId());
        if (exist == null) {
            throw new ServiceException("阈值信息不存在");
        }
        validatePeriod(threshold.getPeriodStart(), threshold.getPeriodEnd());
        checkPeriodConflict(exist.getDeviceCode(), exist.getWarningCode(),
                threshold.getPeriodStart(), threshold.getPeriodEnd(), exist.getId());

        threshold.setCreateTime(exist.getCreateTime());
        threshold.setUpdateTime(LocalDateTime.now());
        return this.updateById(threshold);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteThresholdBatch(List<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            throw new ServiceException("请选择要删除的阈值");
        }
        return this.removeByIds(ids);
    }

    /**
     * 校验分时时间格式与先后关系
     */
    private void validatePeriod(String periodStart, String periodEnd) {
        boolean hasStart = StrUtil.isNotBlank(periodStart);
        boolean hasEnd = StrUtil.isNotBlank(periodEnd);
        if (!hasStart && !hasEnd) {
            return; // 全天生效
        }
        if (!hasStart || !hasEnd) {
            throw new ServiceException("分时阈值的生效开始时间和结束时间必须同时填写");
        }
        if (!TIME_PATTERN.matcher(periodStart.trim()).matches()
                || !TIME_PATTERN.matcher(periodEnd.trim()).matches()) {
            throw new ServiceException("分时时间格式必须为 HH:mm");
        }
        if (periodStart.trim().compareTo(periodEnd.trim()) >= 0) {
            throw new ServiceException("分时阈值生效开始时间必须早于结束时间");
        }
    }

    /**
     * 校验同一设备+预警编码下分时时段不允许重叠
     */
    private void checkPeriodConflict(String deviceCode, String warningCode,
                                     String periodStart, String periodEnd, String excludeId) {
        LambdaQueryWrapper<WarningThreshold> wrapper = new LambdaQueryWrapper<>();
        wrapper.apply("CONCAT(',', device_code, ',') LIKE CONCAT('%,', {0}, ',%')", deviceCode)
                .eq(WarningThreshold::getWarningCode, warningCode);
        if (StrUtil.isNotBlank(excludeId)) {
            wrapper.ne(WarningThreshold::getId, excludeId);
        }
        List<WarningThreshold> exists = this.list(wrapper);
        if (CollUtil.isEmpty(exists)) {
            return;
        }
        for (WarningThreshold exist : exists) {
            if (isPeriodOverlap(periodStart, periodEnd, exist.getPeriodStart(), exist.getPeriodEnd())) {
                throw new ServiceException("设备[" + deviceCode + "]在相同预警编码[" + warningCode
                        + "]下已存在重叠时段阈值，请调整分时设置");
            }
        }
    }

    /**
     * 判断两个时段是否重叠（null 视为全天）
     */
    private boolean isPeriodOverlap(String start1, String end1, String start2, String end2) {
        String s1 = StrUtil.isBlank(start1) ? "00:00" : start1.trim();
        String e1 = StrUtil.isBlank(end1) ? "23:59" : end1.trim();
        String s2 = StrUtil.isBlank(start2) ? "00:00" : start2.trim();
        String e2 = StrUtil.isBlank(end2) ? "23:59" : end2.trim();
        return s1.compareTo(e2) < 0 && s2.compareTo(e1) < 0;
    }
}
