/*
 * Copyright [2022] [https://www.xiaonuo.vip]
 *
 * Snowy采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：
 *
 * 1.请不要删除和修改根目录下的LICENSE文件。
 * 2.请不要删除和修改Snowy源码头部的版权声明。
 * 3.本项目代码可免费商业使用，商业使用请保留源码和相关描述文件的项目出处，作者声明等。
 * 4.分发源码时候，请注明软件出处 https://www.xiaonuo.vip
 * 5.不可二次分发开源参与同类竞品，如有想法可联系团队xiaonuobase@qq.com商议合作。
 * 6.若您的项目无法满足以上几点，需要更多功能代码，获取Snowy商业授权许可，请在官网购买授权，地址为 https://www.xiaonuo.vip
 */
package vip.xiaonuo.coldchain.modular.monitortargetregion.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
import vip.xiaonuo.coldchain.core.config.JfcloudColdChainConstants;
import vip.xiaonuo.coldchain.core.config.JfcloudRedisCacheService;
import vip.xiaonuo.coldchain.modular.monitordevice.entity.MonitorDevice;
import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
import vip.xiaonuo.coldchain.modular.monitortarget.service.MonitorTargetService;
import vip.xiaonuo.coldchain.modular.monitortargetregion.entity.MonitorTargetRegion;
import vip.xiaonuo.coldchain.modular.monitortargetregion.enums.MonitorTargetRegionEvent;
import vip.xiaonuo.coldchain.modular.monitortargetregion.mapper.MonitorTargetRegionMapper;
import vip.xiaonuo.coldchain.modular.monitortargetregion.param.MonitorTargetRegionAddParam;
import vip.xiaonuo.coldchain.modular.monitortargetregion.param.MonitorTargetRegionEditParam;
import vip.xiaonuo.coldchain.modular.monitortargetregion.param.MonitorTargetRegionIdParam;
import vip.xiaonuo.coldchain.modular.monitortargetregion.param.MonitorTargetRegionPageParam;
import vip.xiaonuo.coldchain.modular.monitortargetregion.service.MonitorTargetRegionService;
import vip.xiaonuo.coldchain.modular.targetroom.entity.TargetRoom;
import vip.xiaonuo.common.enums.CommonDeleteFlagEnum;
import vip.xiaonuo.common.enums.CommonSortOrderEnum;
import vip.xiaonuo.common.exception.CommonException;
import vip.xiaonuo.common.listener.CommonDataChangeEventCenter;
import vip.xiaonuo.common.page.CommonPageRequest;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 区域管理Service接口实现类
 *
 * @author 黄渊昊
 * @date 2024/11/13 16:57
 **/
@Service
@Slf4j
public class MonitorTargetRegionServiceImpl extends ServiceImpl<MonitorTargetRegionMapper, MonitorTargetRegion> implements MonitorTargetRegionService {
    @Resource
    private MonitorDeviceService monitorDeviceService;
    @Resource
    private MonitorTargetService monitorTargetService;
    @Resource
    private JfcloudRedisCacheService jfcloudRedisCacheService;

    @Override
    public Page<MonitorTargetRegion> page(MonitorTargetRegionPageParam monitorTargetRegionPageParam) {
        SaBaseLoginUser loginUser = StpLoginUserUtil.getLoginUser();
        QueryWrapper<MonitorTargetRegion> queryWrapper = new QueryWrapper<MonitorTargetRegion>().checkSqlInjection();
        if (ObjectUtil.isNotEmpty(monitorTargetRegionPageParam.getName())) {
            queryWrapper.lambda().like(MonitorTargetRegion::getName, monitorTargetRegionPageParam.getName());
        }
        if (ObjectUtil.isNotEmpty(monitorTargetRegionPageParam.getMonitorTargetId())) {
            queryWrapper.lambda().like(MonitorTargetRegion::getMonitorTargetId, monitorTargetRegionPageParam.getMonitorTargetId());
        }
        if (ObjectUtil.isAllNotEmpty(monitorTargetRegionPageParam.getSortField(), monitorTargetRegionPageParam.getSortOrder())) {
            CommonSortOrderEnum.validate(monitorTargetRegionPageParam.getSortOrder());
            queryWrapper.orderBy(true, monitorTargetRegionPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
                    StrUtil.toUnderlineCase(monitorTargetRegionPageParam.getSortField()));
        } else {
            queryWrapper.lambda().orderByAsc(MonitorTargetRegion::getId);
        }
        queryWrapper.lambda().eq(MonitorTargetRegion::getCreateOrg,loginUser.getOrgId()).eq(MonitorTargetRegion::getDeleteFlag,CommonDeleteFlagEnum.NOT_DELETE);
        Page<MonitorTargetRegion> page = this.page(CommonPageRequest.defaultPage(), queryWrapper);
        for (MonitorTargetRegion monitorTargetRegion : page.getRecords()) {
            String targetId = monitorTargetService.getById(monitorTargetRegion.getMonitorTargetId()).getName();
            monitorTargetRegion.setTargetName(targetId);
            String deviceName = monitorDeviceService.getById(monitorTargetRegion.getMonitorDeviceId()).getDeviceName();
            monitorTargetRegion.setDeviceName(deviceName);
            monitorTargetRegion.setCreateOrgName(loginUser.getOrgName());
        }
        return page;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(MonitorTargetRegionAddParam monitorTargetRegionAddParam) {
        MonitorDevice monitorDevice = monitorDeviceService.queryEntity(monitorTargetRegionAddParam.getMonitorDeviceId());
        Integer sensorCount = monitorDevice.getSensorCount();
        LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MonitorTargetRegion::getMonitorDeviceId,monitorTargetRegionAddParam.getMonitorDeviceId());
        List<MonitorTargetRegion> list = list(queryWrapper);
        for (MonitorTargetRegion monitorTargetRegion : list) {
            if (monitorTargetRegion.getSensorRoute().equals(monitorTargetRegionAddParam.getSensorRoute())) {
                throw new CommonException("该传感器路数已被使用，请重新选择");
            }
        }
        if (monitorTargetRegionAddParam.getSensorRoute() > sensorCount) {
            throw new CommonException("路数超出限制，请重新选择");
        }
        MonitorTargetRegion monitorTargetRegion = BeanUtil.toBean(monitorTargetRegionAddParam, MonitorTargetRegion.class);
        this.save(monitorTargetRegion);
        CommonDataChangeEventCenter.doUpdateWithData(MonitorTargetRegionEvent.MONITOR_TARGET_REGION_EDIT_EVENT, JSONUtil.createArray().put(monitorTargetRegion));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void edit(MonitorTargetRegionEditParam monitorTargetRegionEditParam) {
        MonitorTargetRegion monitorTargetRegion = this.queryEntity(monitorTargetRegionEditParam.getId());
        if(Objects.isNull(monitorTargetRegion)) {
            return;
        }
        BeanUtil.copyProperties(monitorTargetRegionEditParam, monitorTargetRegion);
        this.updateById(monitorTargetRegion);
        jfcloudRedisCacheService.evictMonitorTargetRegionCache(monitorTargetRegion.getDeviceCode(),monitorTargetRegion.getSensorRoute());
        CommonDataChangeEventCenter.doUpdateWithData(MonitorTargetRegionEvent.MONITOR_TARGET_REGION_EDIT_EVENT, JSONUtil.createArray().put(monitorTargetRegion));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(List<MonitorTargetRegionIdParam> monitorTargetRegionIdParamList) {
        for (MonitorTargetRegionIdParam monitorTargetRegionIdParam : monitorTargetRegionIdParamList) {
            LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(MonitorTargetRegion::getParentId, monitorTargetRegionIdParam.getId());
            List<MonitorTargetRegion> list = list(queryWrapper);
            if (!list.isEmpty()) {
                throw new CommonException("该区域下存在子区域，请删除子区域后再试");
            }
        }
        // 执行删除
        this.removeByIds(CollStreamUtil.toList(monitorTargetRegionIdParamList, MonitorTargetRegionIdParam::getId));
    }

    @Override
    public MonitorTargetRegion detail(MonitorTargetRegionIdParam monitorTargetRegionIdParam) {
        return this.queryEntity(monitorTargetRegionIdParam.getId());
    }

    @Override
    public MonitorTargetRegion queryEntity(String id) {
        MonitorTargetRegion monitorTargetRegion = this.getById(id);
        if (ObjectUtil.isEmpty(monitorTargetRegion)) {
            throw new CommonException("区域管理不存在，id值为：{}", id);
        }
        return monitorTargetRegion;
    }

    @Override
    public List<MonitorTargetRegion> getRegionListByTargetId(String targetId) {
        LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<MonitorTargetRegion>();
        queryWrapper.eq(MonitorTargetRegion::getMonitorTargetId, targetId).eq(MonitorTargetRegion::getDeleteFlag,CommonDeleteFlagEnum.NOT_DELETE);;
        List<MonitorTargetRegion> list = list(queryWrapper);
        for (MonitorTargetRegion monitorTargetRegion : list) {
            MonitorDevice monitorDevice = monitorDeviceService.getById(monitorTargetRegion.getMonitorDeviceId());
            if (ObjectUtil.isNull(monitorDevice)) {
                throw new CommonException("找不到监控设备，请检查监控对象是否有误");
            }
            monitorTargetRegion.setDeviceName(monitorDevice.getDeviceName());
        }
        return list;
    }

    @Override
    public List<Tree<String>> tree() {
        List<MonitorTargetRegion> monitorTargetRegionList = list();

        this.fillParentLocationInfo(monitorTargetRegionList);

        for (MonitorTargetRegion monitorTargetRegion : monitorTargetRegionList) {
            MonitorTargetRegion byId = this.getById(monitorTargetRegion.getParentId());
            if(ObjectUtil.isNotEmpty(byId)) {
                monitorTargetRegion.setParentId(byId.getId());
            }
            String targetId = monitorTargetService.getById(monitorTargetRegion.getMonitorTargetId()).getName();
            monitorTargetRegion.setTargetName(targetId);
            String deviceName = monitorDeviceService.getById(monitorTargetRegion.getMonitorDeviceId()).getDeviceName();
            monitorTargetRegion.setDeviceName(deviceName);
        }

        List<TreeNode<String>> treeNodeList = monitorTargetRegionList.stream().map(locationType ->
                        new TreeNode<>(locationType.getId(), locationType.getParentId(),
                                locationType.getName(), 0).setExtra(JSONUtil.parseObj(locationType)))
                .collect(Collectors.toList());
        List<Tree<String>> build = TreeUtil.build(treeNodeList, "0");
        return build;
    }

    @Override
    public List<String> getTargetIdListByDeviceId(String deviceCode) {
        LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MonitorTargetRegion::getDeviceCode,deviceCode).eq(MonitorTargetRegion::getDeleteFlag,CommonDeleteFlagEnum.NOT_DELETE);;
        List<String> targetIdList = new ArrayList<>();
        for (MonitorTargetRegion monitorTargetRegion : list(queryWrapper)) {
            targetIdList.add(monitorTargetRegion.getMonitorTargetId());
        }
        return targetIdList;
    }

    @Override
    public List<MonitorTargetRegion> listByTargetId(String targetId) {
        LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MonitorTargetRegion::getMonitorTargetId, targetId).eq(MonitorTargetRegion::getDeleteFlag,CommonDeleteFlagEnum.NOT_DELETE);;
        return list(queryWrapper);
    }

    @Override
    @Cacheable(value = JfcloudColdChainConstants.MONITORTARGETREGION_CACHE_NAME, key = "#deviceCode + ':' + #sensorNo", unless = "#result == null")
    public MonitorTargetRegion findOneByDeviceCodeAndSensorNo(String deviceCode, Integer sensorNo) {
        // 查询条件封装
        LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MonitorTargetRegion::getDeviceCode, deviceCode)
                .eq(MonitorTargetRegion::getSensorRoute, sensorNo)
                .eq(MonitorTargetRegion::getDeleteFlag, CommonDeleteFlagEnum.NOT_DELETE);
        MonitorTargetRegion monitorTargetRegion = getOne(queryWrapper);
        if (Objects.isNull(monitorTargetRegion)) {
            log.info("未找到设备编号 [{}] 和传感器编号 [{}] 对应的监控目标区域。", deviceCode, sensorNo);
            return null;
        }
        // 获取房间名称
        String monitorTargetId = monitorTargetRegion.getMonitorTargetId();
        if (Objects.nonNull(monitorTargetId)) {
            TargetRoom roomName = monitorTargetService.getRoomByMonitorTargetId(monitorTargetId);
            if (Objects.nonNull(roomName)) {
                monitorTargetRegion.setRoomName(roomName.getName());
                log.info("成功获取房间名称 [{}]，并设置到监控目标区域。", roomName.getName());
            } else {
                log.info("未找到监控目标 ID [{}] 对应的房间名称。", monitorTargetId);
            }
        } else {
            log.info("监控目标区域 [{}] 的监控目标 ID 为空，跳过房间名称查询。", monitorTargetRegion.getId());
        }
        return monitorTargetRegion;
    }


    @Override
    public Integer getUsedSensorCount() {
        SaBaseLoginUser loginUser = StpLoginUserUtil.getLoginUser();
        LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MonitorTargetRegion::getDeleteFlag,CommonDeleteFlagEnum.NOT_DELETE)
                .eq(MonitorTargetRegion::getCreateOrg,loginUser.getOrgId());
        return (int)count(queryWrapper);
    }

    @Override
    public List<MonitorTargetRegion> listByDeviceId(String deviceId) {
        LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<>();
        if (deviceId != null) {
            queryWrapper.eq(MonitorTargetRegion::getMonitorDeviceId, deviceId);
        }
        queryWrapper.eq(MonitorTargetRegion::getDeleteFlag,CommonDeleteFlagEnum.NOT_DELETE);
        return list(queryWrapper);
    }

    @Override
    public List<String> listByDeviceCodeByTargetId(String targetId) {
        LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MonitorTargetRegion::getMonitorTargetId, targetId).eq(MonitorTargetRegion::getDeleteFlag, CommonDeleteFlagEnum.NOT_DELETE);
        List<MonitorTargetRegion> list = list(queryWrapper);
        List<String> codeList = new ArrayList<>();
        list.forEach(code -> codeList.add(code.getDeviceCode()));
        return codeList;
    }

    private void fillParentLocationInfo(List<MonitorTargetRegion> resourceList) {
        if (CollUtil.isNotEmpty(resourceList)) {
            List<MonitorTargetRegion> locationTypes = resourceList.stream().filter(distinctByKey(MonitorTargetRegion::getParentId)).collect(Collectors.toList());
            List<String> parentIds = null;
            if (CollUtil.isNotEmpty(locationTypes)) {
                parentIds = CollUtil.newArrayList();
                for (MonitorTargetRegion locationType : locationTypes) {
                    if (!StrUtil.equals(locationType.getParentId(), "-1")) {
                        parentIds.add(locationType.getParentId());
                    }
                }
            }
            if (CollUtil.isNotEmpty(parentIds)) {
                LambdaQueryWrapper<MonitorTargetRegion> locationTypeLambdaQueryWrapper = new LambdaQueryWrapper<>();
                locationTypeLambdaQueryWrapper.in(MonitorTargetRegion::getId, parentIds);
                List<MonitorTargetRegion> parentLocationMenus = this.list(locationTypeLambdaQueryWrapper);
                if (CollUtil.isNotEmpty(parentLocationMenus)) {
                    this.fillParentLocationInfo(parentLocationMenus);
                    resourceList.addAll(parentLocationMenus);
                }
            }
        }
    }

    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }
}
