package com.zcloud.personnel.positioning.command;

import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zcloud.personnel.positioning.dto.FenceUpdateCmd;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleFenceRelDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleFenceRelRepository;
import com.zcloud.personnel.positioning.persistence.repository.FenceRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

@Component
@AllArgsConstructor
public class FenceUpdateExe {
    private final FenceRepository fenceRepository;
    private final AlarmRuleFenceRelRepository alarmRuleFenceRelRepository;
    private final AlarmRuleUpdateExe alarmRuleUpdateExe;
    private final FencePersistenceSupport fencePersistenceSupport;
    private final FindsPendingSyncService findsPendingSyncService;
    private final FencePointPersistenceService fencePointPersistenceService;

    @Transactional(rollbackFor = Exception.class)
    public void execute(FenceUpdateCmd cmd) {
        if (cmd.getId() == null) {
            throw new BizException("Fence id is required");
        }
        FenceDO fenceDO = fenceRepository.getById(cmd.getId());
        if (fenceDO == null) {
            throw new BizException("Fence does not exist");
        }
        BeanUtils.copyProperties(cmd, fenceDO, getNullPropertyNames(cmd));
        fencePersistenceSupport.normalize(fenceDO, false);
        fenceDO.setSyncStatus(FindsPendingSyncService.STATUS_WAIT);
        fenceDO.setSyncToken(FindsSyncToken.next());
        boolean res = fenceRepository.updateById(fenceDO);
        if (!res) {
            throw new BizException("Fence update failed");
        }
        fencePointPersistenceService.rebuild(fenceDO);
        findsPendingSyncService.scheduleFence(fenceDO.getId());
    }

    @Transactional(rollbackFor = Exception.class)
    public void changeStatus(Long id, boolean enabled) {
        if (id == null) {
            throw new BizException("Fence id is required");
        }
        FenceDO fenceDO = fenceRepository.getById(id);
        if (fenceDO == null) {
            throw new BizException("Fence does not exist");
        }
        fenceDO.setStatus(enabled ? AlarmRuleConfigPersistence.STATUS_ENABLED : AlarmRuleConfigPersistence.STATUS_DISABLED);
        fenceDO.setSyncToken(FindsSyncToken.next());
        boolean res = fenceRepository.updateById(fenceDO);
        if (!res) {
            throw new BizException("Fence status update failed");
        }
        for (Long ruleId : listRelatedRuleIds(fenceDO)) {
            alarmRuleUpdateExe.changeStatus(ruleId, enabled);
        }
    }

    private Set<Long> listRelatedRuleIds(FenceDO fenceDO) {
        QueryWrapper<AlarmRuleFenceRelDO> queryWrapper = new QueryWrapper<>();
        if (fenceDO.getFindsFenceId() == null) {
            queryWrapper.eq("fence_id", fenceDO.getId());
        } else {
            queryWrapper.and(wrapper -> wrapper.eq("fence_id", fenceDO.getId())
                    .or()
                    .eq("finds_fence_id", fenceDO.getFindsFenceId()));
        }
        queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        List<AlarmRuleFenceRelDO> relList = alarmRuleFenceRelRepository.list(queryWrapper);
        Set<Long> ruleIds = new LinkedHashSet<>();
        if (relList == null) {
            return ruleIds;
        }
        for (AlarmRuleFenceRelDO rel : relList) {
            if (rel.getRuleId() != null) {
                ruleIds.add(rel.getRuleId());
            }
        }
        return ruleIds;
    }

    private String[] getNullPropertyNames(Object source) {
        BeanWrapper src = new BeanWrapperImpl(source);
        PropertyDescriptor[] pds = src.getPropertyDescriptors();
        Set<String> emptyNames = new HashSet<>();
        for (PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                emptyNames.add(pd.getName());
            }
        }
        return emptyNames.toArray(new String[0]);
    }
}
