package org.springblade.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.UUID;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.common.constant.CommonConstant;
import org.springblade.core.tool.utils.DigestUtil;
import org.springblade.system.cache.ParamCache;
import org.springblade.system.convert.DeptConvert;
import org.springblade.system.convert.UserConvert;
import org.springblade.system.log.ComposeLogUtil;
import org.springblade.system.pojo.entity.DataSyncRecord;
import org.springblade.system.pojo.entity.Dept;
import org.springblade.system.pojo.entity.User;
import org.springblade.system.pojo.enums.DataSync;
import org.springblade.system.pojo.enums.DeptCategory;
import org.springblade.system.service.IDataSyncRecordService;
import org.springblade.system.service.IDeptService;
import org.springblade.system.service.IMKPushService;
import org.springblade.system.service.IUserService;
import org.springblade.system.util.DataSyncRecordUtils;
import org.springblade.thirdparty.mk.pojo.dto.MKPushDTO;
import org.springblade.thirdparty.mk.pojo.dto.MKPushOrgDeptDTO;
import org.springblade.thirdparty.mk.pojo.dto.MKPushPersonDTO;
import org.springblade.thirdparty.mk.service.IMKService;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

import static org.springblade.common.constant.CommonConstant.DEFAULT_PARAM_PASSWORD;

/**
 *  mk 推送实现
 * @author bfhuange
 * @date 2024/9/24
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class MKPushServiceImpl implements IMKPushService {
    /**
     *  分页查询每页最大大小
     */
    private static final long MAX_SIZE_PER_PAGE = 1000;

    private final IDeptService deptService;
    private final IUserService userService;
    private final IMKService mkService;
    private final DeptConvert deptConvert;
    private final UserConvert userConvert;
    private final IDataSyncRecordService dataSyncRecordService;

    @Override
    public void pushOrgAndRecord(Date oaStartTime) {
        try {
            ComposeLogUtil.addLog(log);
            // 推送所有公司和部门并记录
            this.pushAndRecord(oaStartTime, DataSyncRecordUtils::createMKOrgPush, (startTime, endTime) -> {
                // 1. 推送公司
                pushDept(DeptCategory.COMPANY, startTime, endTime);
                // 2. 推送部门
                pushDept(DeptCategory.DEPT, startTime, endTime);
            });
        } finally {
            ComposeLogUtil.removeLastLog();
        }
    }

    @Override
    public void pushPersonAndRecord(Date oaStartTime) {
        try {
            ComposeLogUtil.addLog(log);
            // 推送人员并记录
            this.pushAndRecord(oaStartTime, DataSyncRecordUtils::createMKPersonPush, (startTime, endTime) -> {
                // 分页查询函数
                UnaryOperator<IPage<User>> queryUser = this.getQueryUser(startTime, endTime);
                // 分页推送函数
                BiConsumer<IPage<User>, String> pushConsumer = this::pushUser;
                // 公共推送
                pushCommon(queryUser, pushConsumer);
            });
        } finally {
            ComposeLogUtil.removeLastLog();
        }
    }

    /**
     *  推送并记录
     * @param oaStartTime
     * @param recordSupplier
     * @param pushHandler
     */
    private void pushAndRecord(Date oaStartTime, Supplier<DataSyncRecord> recordSupplier, BiConsumer<Date, Date> pushHandler) {
        // 创建记录
        DataSyncRecord record = recordSupplier.get();
        // 查询最新一条成功记录
        DataSyncRecord lastSuccessRecord = dataSyncRecordService.getLastSuccessRecord(record);
        // 获取推送数据的开始时间
        Date startTime = getStartTime(lastSuccessRecord, oaStartTime);
        // 推送开始时间作为截止时间，只同步这个时间段的更新的数据
        Date endTime = new Date();
        record.setSyncTime(endTime);
        try {
            pushHandler.accept(startTime, endTime);
        } catch (Exception e) {
            // 有异常，结果改成失败
            record.setResult(DataSync.Result.FAIL.name());
            throw e;
        } finally {
            // 新事务保存同步记录
            dataSyncRecordService.saveWithNewTransaction(record);
        }
    }

    /**
     *  获取推送开始时间
     * @param lastSuccessRecord 最后一条同步成功的记录
     * @param oaStartTime oa同步查询开始时间
     * @return
     */
    private Date getStartTime(DataSyncRecord lastSuccessRecord, Date oaStartTime) {
        if (lastSuccessRecord == null || lastSuccessRecord.getSyncTime() == null) {
            // 最后一条推送成功记录为空，返回空推送全部
            return null;
        }
        if (oaStartTime == null) {
            // oa 同步查询开始时间为空，且最后一条推送成功记录不为空
            return lastSuccessRecord.getSyncTime();
        }
        // 否则取2个时间较小的那个
        Date lastSyncTime = lastSuccessRecord.getSyncTime();
        return oaStartTime.before(lastSyncTime) ? oaStartTime : lastSyncTime;
    }

    /**
     * 同步公司/部门
     *
     * @param deptCategory 类型
     * @param startTime    数据更新开始时间
     * @param endTime   数据更新截止时间
     */
    private void pushDept(DeptCategory deptCategory, Date startTime, Date endTime) {
        // 分页查询函数
        UnaryOperator<IPage<Dept>> queryDept = getQueryDept(deptCategory, startTime, endTime);
        // 推送函数
        BiConsumer<IPage<Dept>, String> pushDept = getPushDept(deptCategory);
        // 公共推送
        pushCommon(queryDept, pushDept);
    }

    /**
     * 获取查询部门的方法
     *
     * @param deptCategory
     * @param startTime
     * @param endTime
     * @return
     */
    private UnaryOperator<IPage<Dept>> getQueryDept(DeptCategory deptCategory, Date startTime, Date endTime) {
        return page -> deptService.page(page, Wrappers.<Dept>lambdaQuery()
                .eq(Dept::getDeptCategory, deptCategory.getCode())
                .eq(Dept::getIsOa, CommonConstant.YES)
                // 查询同步时间小于结束时间且修改时间小于结束时间的部门
                .lt(Dept::getSyncTime, endTime)
                .lt(Dept::getUpdateTime, endTime)
                // 开始时间不为空，查询同步时间大于开始时间或修改时间大于开始时间的数据
                .and(startTime != null, wrapper -> wrapper.ge(Dept::getSyncTime, startTime)
                        .or(w -> w.ge(Dept::getUpdateTime, startTime)))
        );
    }

    /**
     *  获取推送部门的方法
     * @param deptCategory
     * @return
     */
    private BiConsumer<IPage<Dept>, String> getPushDept(DeptCategory deptCategory) {
        return (page, batchId) -> {
            List<Dept> records = page.getRecords();
            if (CollectionUtil.isEmpty(records)) {
                return;
            }
            // 当前页是否最后一页
            boolean lastPage = page.getCurrent() == page.getPages();
            MKPushDTO<MKPushOrgDeptDTO> param = new MKPushDTO<>();
            param.setBatchId(batchId);
            param.setFinish(lastPage);
            List<MKPushOrgDeptDTO> list = deptConvert.entities2dtos(records);
            param.setMessage(list);
            // 推送公司
            boolean result = mkService.pushOrgDept(param);
            ComposeLogUtil.getLastLog().info("mk推送{} 批次号：{} 当前页：{} 当前页数量：{} 结果：{}", deptCategory.getName(), batchId, page.getCurrent(), page.getSize(), result);
        };
    }


    /**
     * 获取查询人员的方法
     *
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @return
     */
    private UnaryOperator<IPage<User>> getQueryUser(Date startTime, Date endTime) {
        return page -> userService.page(page, Wrappers.<User>lambdaQuery()
                .eq(User::getIsOa, CommonConstant.YES)
                // 查询同步时间小于结束时间且修改时间小于结束时间的用户
                .lt(User::getSyncTime, endTime)
                .lt(User::getUpdateTime, endTime)
                // 开始时间不为空，查询同步时间大于更新时间或修改时间大于更新时间的数据
                .and(startTime != null, wrapper -> wrapper.ge(User::getSyncTime, startTime)
                        .or(w -> w.ge(User::getUpdateTime, startTime)))
        );
    }

    /**
     *  推送人员
     * @param page
     * @param batchId
     */
    private void pushUser(IPage<User> page, String batchId) {
        List<User> records = page.getRecords();
        if (CollectionUtil.isEmpty(records)) {
            return;
        }
        // 当前页是否最后一页
        boolean lastPage = page.getCurrent() == page.getPages();
        MKPushDTO<MKPushPersonDTO> param = new MKPushDTO<>();
        param.setBatchId(batchId);
        param.setFinish(lastPage);
        // 默认密码及参数转换
        String defaultPassword = ParamCache.getValue(DEFAULT_PARAM_PASSWORD);
        // 解密手机号
        records.forEach(userService::decryptPhone);
        List<MKPushPersonDTO> list = userConvert.entities2dtos(records, DigestUtil.md5Hex(defaultPassword));
        param.setMessage(list);
        // 推送人员
        boolean result = mkService.pushPerson(param);
        ComposeLogUtil.getLastLog().info("mk推送人员 批次号：{} 当前页：{} 当前页数量：{} 结果：{}", batchId, page.getCurrent(), page.getSize(), result);
        if (result) {
            // 推送成功，修改用户推送状态
            List<Long> updateIds = records.stream()
                    .map(User::getId)
                    .toList();
            userService.lambdaUpdate()
                    // 设置是否推送mk为是
                    .set(User::getIsPushMk, CommonConstant.YES)
                    .in(User::getId, updateIds)
                    // 筛选未推送mk的
                    .eq(User::getIsPushMk, CommonConstant.NO)
                    .update();
        }
    }

    /**
     *  公共推送
     * @param queryOperator 分页查询
     * @param pushConsumer 数据推送
     * @param <T>
     */
    private <T> void pushCommon(UnaryOperator<IPage<T>> queryOperator, BiConsumer<IPage<T>, String> pushConsumer) {
        IPage<T> page = new Page<>(1, MAX_SIZE_PER_PAGE);
        String batchId = UUID.fastUUID().toString(true);
        do {
            // 分页查询数据
            page = queryOperator.apply(page);
            // 推送数据
            pushConsumer.accept(page, batchId);
            page.setCurrent(page.getCurrent() + 1);
        } while (page.getCurrent() <= page.getPages());
    }
}
