package com.hotwj.platform.securityManagement.trainingCourseRecord.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hotwj.platform.operationManagement.hourPurchaseDetail.domain.HotHourPurchaseDetail;
import com.hotwj.platform.operationManagement.hourPurchaseDetail.mapper.HotHourPurchaseDetailMapper;
import com.hotwj.platform.operationManagement.hourUsageDetail.domain.HotHourUsageDetail;
import com.hotwj.platform.operationManagement.hourUsageDetail.mapper.HotHourUsageDetailMapper;
import com.hotwj.platform.securityManagement.training.domain.HotTraining;
import com.hotwj.platform.securityManagement.training.mapper.HotTrainingMapper;
import com.hotwj.platform.securityManagement.trainingCourseConfig.domain.HotTrainingCourseConfig;
import com.hotwj.platform.securityManagement.trainingCourseConfig.mapper.HotTrainingCourseConfigMapper;
import com.hotwj.platform.securityManagement.trainingCourseRecord.domain.HotTrainingCourseRecord;
import com.hotwj.platform.securityManagement.trainingCourseRecord.domain.bo.HotTrainingCourseRecordBo;
import com.hotwj.platform.securityManagement.trainingCourseRecord.domain.vo.HotTrainingCourseRecordVo;
import com.hotwj.platform.securityManagement.trainingCourseRecord.domain.vo.PersonalAnnualRecordCourseVo;
import com.hotwj.platform.securityManagement.trainingCourseRecord.mapper.HotTrainingCourseRecordMapper;
import com.hotwj.platform.securityManagement.trainingCourseRecord.service.IHotTrainingCourseRecordService;
import com.hotwj.platform.securityManagement.training.support.StudyForbidPeriodSupport;
import com.hotwj.platform.securityManagement.trainingProgress.service.TrainingProgressService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;

/**
 * 培训人员课程学习记录Service业务层处理
 *
 * @author shihongwei
 * @date 2026-02-02
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class HotTrainingCourseRecordServiceImpl implements IHotTrainingCourseRecordService {

    private static final Long PURCHASE_DETAIL_STATUS_EFFECTIVE = 1L;
    private static final Long PURCHASE_DETAIL_STATUS_USED_UP = 2L;
    private static final Long USE_HOUR_PACKAGE_NO = 0L;
    private static final Long USE_HOUR_PACKAGE_YES = 1L;
    private static final String TRAINING_TYPE_DAILY = "daily";

    private final HotTrainingCourseRecordMapper baseMapper;
    private final TrainingProgressService trainingProgressService;
    private final HotHourPurchaseDetailMapper hotHourPurchaseDetailMapper;
    private final HotHourUsageDetailMapper hotHourUsageDetailMapper;
    private final HotTrainingMapper hotTrainingMapper;
    private final HotTrainingCourseConfigMapper hotTrainingCourseConfigMapper;
    private final StudyForbidPeriodSupport studyForbidPeriodSupport;

    /**
     * 查询培训人员课程学习记录
     *
     * @param id 主键
     * @return 培训人员课程学习记录
     */
    @Override
    public HotTrainingCourseRecordVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    /**
     * 分页查询培训人员课程学习记录列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 培训人员课程学习记录分页列表
     */
    @Override
    public TableDataInfo<HotTrainingCourseRecordVo> queryPageList(HotTrainingCourseRecordBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<HotTrainingCourseRecord> lqw = buildQueryWrapper(bo);
        Page<HotTrainingCourseRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的培训人员课程学习记录列表
     *
     * @param bo 查询条件
     * @return 培训人员课程学习记录列表
     */
    @Override
    public List<HotTrainingCourseRecordVo> queryList(HotTrainingCourseRecordBo bo) {
        LambdaQueryWrapper<HotTrainingCourseRecord> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    /**
     * 查询某个用户在某培训某课程下最新的一条课程学习记录
     *
     * @param bo 查询条件（需至少包含 companyId、trainingId、courseId、userId）
     * @return 最新的一条课程学习记录；若不存在则返回 null
     */
    @Override
    public HotTrainingCourseRecordVo queryLatestByUserAndCourse(HotTrainingCourseRecordBo bo) {
        LambdaQueryWrapper<HotTrainingCourseRecord> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getCompanyId() != null, HotTrainingCourseRecord::getCompanyId, bo.getCompanyId());
        lqw.eq(bo.getTrainingId() != null, HotTrainingCourseRecord::getTrainingId, bo.getTrainingId());
        lqw.eq(StringUtils.isNotBlank(bo.getUserId()), HotTrainingCourseRecord::getUserId, bo.getUserId());
        lqw.eq(bo.getTrainingCourseId() != null, HotTrainingCourseRecord::getTrainingCourseId, bo.getTrainingCourseId());
        lqw.orderByDesc(HotTrainingCourseRecord::getCompleteTime);
        lqw.orderByDesc(HotTrainingCourseRecord::getCreateTime);
        Page<HotTrainingCourseRecordVo> page = baseMapper.selectVoPage(new Page<>(1, 1), lqw);
        List<HotTrainingCourseRecordVo> records = page.getRecords();
        if (records == null || records.isEmpty()) {
            return null;
        }
        return records.get(0);
    }

    @Override
    public List<PersonalAnnualRecordCourseVo> queryPersonalAnnualRecordCourses(String userId, Date startTime, Date endTime, String trainingType) {
        return baseMapper.selectPersonalAnnualRecordCourses(userId, startTime, endTime, trainingType);
    }

    private LambdaQueryWrapper<HotTrainingCourseRecord> buildQueryWrapper(HotTrainingCourseRecordBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<HotTrainingCourseRecord> lqw = Wrappers.lambdaQuery();
        lqw.orderByDesc(HotTrainingCourseRecord::getCreateTime);
        lqw.eq(bo.getCompanyId() != null, HotTrainingCourseRecord::getCompanyId, bo.getCompanyId());
        lqw.eq(bo.getTrainingId() != null, HotTrainingCourseRecord::getTrainingId, bo.getTrainingId());
        lqw.eq(StringUtils.isNotBlank(bo.getUserId()), HotTrainingCourseRecord::getUserId, bo.getUserId());
        lqw.like(StringUtils.isNotBlank(bo.getUserName()), HotTrainingCourseRecord::getUserName, bo.getUserName());
        lqw.eq(bo.getTrainingCourseId() != null, HotTrainingCourseRecord::getTrainingCourseId, bo.getTrainingCourseId());
        lqw.eq(bo.getExamId() != null, HotTrainingCourseRecord::getExamId, bo.getExamId());
        lqw.eq(bo.getExamScore() != null, HotTrainingCourseRecord::getExamScore, bo.getExamScore());
        lqw.eq(bo.getCheckStatus() != null, HotTrainingCourseRecord::getCheckStatus, bo.getCheckStatus());
        lqw.eq(bo.getStudyDuration() != null, HotTrainingCourseRecord::getStudyDuration, bo.getStudyDuration());
        lqw.eq(bo.getProgressRate() != null, HotTrainingCourseRecord::getProgressRate, bo.getProgressRate());
        lqw.eq(bo.getLearnDurationMin() != null, HotTrainingCourseRecord::getLearnDurationMin, bo.getLearnDurationMin());
        lqw.eq(bo.getCompleteTime() != null, HotTrainingCourseRecord::getCompleteTime, bo.getCompleteTime());
        lqw.like(StringUtils.isNotBlank(bo.getCreateByName()), HotTrainingCourseRecord::getCreateByName, bo.getCreateByName());
        lqw.like(StringUtils.isNotBlank(bo.getUpdateByName()), HotTrainingCourseRecord::getUpdateByName, bo.getUpdateByName());
        lqw.eq(bo.getIsDeleted() != null, HotTrainingCourseRecord::getIsDeleted, bo.getIsDeleted());
        return lqw;
    }

    /**
     * 新增培训人员课程学习记录
     *
     * @param bo 培训人员课程学习记录
     * @return 是否新增成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long insertByBo(HotTrainingCourseRecordBo bo) {
        if (Boolean.TRUE.equals(bo.getCheckForbidStudy())) {
            studyForbidPeriodSupport.checkStudyAllowed(bo.getCompanyId());
        }
        initHourPackageUsageFieldsForInsert(bo);
        HotTrainingCourseRecord add = MapstructUtils.convert(bo, HotTrainingCourseRecord.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }

        if (flag && bo.getCompanyId() != null && bo.getTrainingId() != null && StringUtils.isNotBlank(bo.getUserId())) {
            trainingProgressService.syncProgress(bo.getCompanyId(), bo.getTrainingId(), bo.getUserId());
        }

        return add.getId();
    }

    /**
     * 修改培训人员课程学习记录
     *
     * @param bo 培训人员课程学习记录
     * @return 是否修改成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateByBo(HotTrainingCourseRecordBo bo) {
        HotTrainingCourseRecord update = MapstructUtils.convert(bo, HotTrainingCourseRecord.class);
        HotTrainingCourseRecord before = null;
        Long companyId = bo.getCompanyId();
        if (bo.getId() != null) {
            before = baseMapper.selectById(bo.getId());
            if (companyId == null && before != null) {
                companyId = before.getCompanyId();
            }
            if (before != null && before.getProgressRate() != null && update.getProgressRate() != null) {
                // 仅操作单条记录时，确保视频进度单调不下降
                if (update.getProgressRate().compareTo(before.getProgressRate()) < 0) {
                    update.setProgressRate(before.getProgressRate());
                }
            }
            if (before != null && before.getLearnDurationMin() != null && update.getLearnDurationMin() != null) {
                // 确保学习时长（秒）单调不下降，避免前端误传0导致统计为0
                if (update.getLearnDurationMin() < before.getLearnDurationMin()) {
                    update.setLearnDurationMin(before.getLearnDurationMin());
                }
            }
        }
        if (Boolean.TRUE.equals(bo.getCheckForbidStudy())) {
            studyForbidPeriodSupport.checkStudyAllowed(companyId);
        }
        initHourPackageUsageFieldsForUpdate(update, before);
        validEntityBeforeSave(update);
        boolean ok = baseMapper.updateById(update) > 0;
        if (ok && before != null && before.getCompanyId() != null && before.getTrainingId() != null && StringUtils.isNotBlank(before.getUserId())) {
            trainingProgressService.syncProgress(before.getCompanyId(), before.getTrainingId(), before.getUserId());
        }
        return ok;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deductHourPackage(Long id) {
        if (id == null) {
            throw new ServiceException("学习记录ID不能为空");
        }
        HotTrainingCourseRecord record = baseMapper.selectById(id);
        if (record == null) {
            throw new ServiceException("学习记录不存在");
        }
        if (Objects.equals(record.getIsUseHourPackage(), USE_HOUR_PACKAGE_YES)) {
            return true;
        }

        Long needUseHours = resolveNeedUseHours(record.getTrainingCourseId());
        if (!canDeductHourPackage(record, needUseHours)) {
            throw new ServiceException("当前学习记录未满足课时套餐扣减条件");
        }

        HotTrainingCourseRecord update = new HotTrainingCourseRecord();
        update.setIsUseHourPackage(USE_HOUR_PACKAGE_YES);
        update.setHourPackageUsedHours(needUseHours);
        boolean updated = baseMapper.update(update, Wrappers.<HotTrainingCourseRecord>lambdaUpdate()
                .eq(HotTrainingCourseRecord::getId, id)
                .eq(HotTrainingCourseRecord::getIsDeleted, USE_HOUR_PACKAGE_NO)
                .and(wrapper -> wrapper.isNull(HotTrainingCourseRecord::getIsUseHourPackage)
                        .or()
                        .eq(HotTrainingCourseRecord::getIsUseHourPackage, USE_HOUR_PACKAGE_NO))) > 0;
        if (!updated) {
            HotTrainingCourseRecord latest = baseMapper.selectById(id);
            if (latest != null && Objects.equals(latest.getIsUseHourPackage(), USE_HOUR_PACKAGE_YES)) {
                return true;
            }
            throw new ServiceException("学习记录状态已变更，请刷新后重试");
        }

        record.setIsUseHourPackage(USE_HOUR_PACKAGE_YES);
        record.setHourPackageUsedHours(needUseHours);
        saveHourPackageUsageIfNeeded(record);
        return true;
    }

    private void initHourPackageUsageFieldsForInsert(HotTrainingCourseRecordBo bo) {
        if (bo == null) {
            return;
        }
        if (bo.getIsUseHourPackage() == null) {
            bo.setIsUseHourPackage(USE_HOUR_PACKAGE_NO);
        }
        if (bo.getHourPackageUsedHours() == null) {
            bo.setHourPackageUsedHours(0L);
        }
    }

    private void initHourPackageUsageFieldsForUpdate(HotTrainingCourseRecord update, HotTrainingCourseRecord before) {
        if (before != null && update.getIsUseHourPackage() == null) {
            update.setIsUseHourPackage(before.getIsUseHourPackage());
        }
        if (before != null && update.getHourPackageUsedHours() == null) {
            update.setHourPackageUsedHours(before.getHourPackageUsedHours());
        }
    }

    private boolean canDeductHourPackage(HotTrainingCourseRecord record, Long needUseHours) {
        if (record == null || record.getCompanyId() == null || record.getTrainingId() == null || record.getTrainingCourseId() == null || StringUtils.isBlank(record.getUserId())) {
            return false;
        }
        if (Objects.equals(record.getIsUseHourPackage(), USE_HOUR_PACKAGE_YES)) {
            return false;
        }
        if (!Objects.equals(record.getCheckStatus(), 1L)) {
            return false;
        }
        HotTraining training = hotTrainingMapper.selectById(record.getTrainingId());
        if (training == null || !StringUtils.equals(TRAINING_TYPE_DAILY, training.getTrainingType())) {
            return false;
        }
        return needUseHours != null && needUseHours > 0;
    }

    private Long resolveNeedUseHours(Long trainingCourseId) {
        if (trainingCourseId == null) {
            return 0L;
        }
        HotTrainingCourseConfig courseConfig = hotTrainingCourseConfigMapper.selectById(trainingCourseId);
        if (courseConfig == null || courseConfig.getStudyDuration() == null || courseConfig.getStudyDuration() <= 0) {
            return 0L;
        }
        return courseConfig.getStudyDuration();
    }

    private void saveHourPackageUsageIfNeeded(HotTrainingCourseRecord record) {
        if (record == null || record.getId() == null) {
            return;
        }
        if (!Objects.equals(record.getIsUseHourPackage(), USE_HOUR_PACKAGE_YES)) {
            return;
        }
        Long needUseHours = record.getHourPackageUsedHours();
        if (needUseHours == null || needUseHours <= 0) {
            return;
        }
        long usedTotal = hotHourUsageDetailMapper.selectList(Wrappers.<HotHourUsageDetail>lambdaQuery()
                        .eq(HotHourUsageDetail::getBizOrderId, record.getId())
                        .eq(HotHourUsageDetail::getUseType, buildUseType(record.getTrainingId()))
                        .eq(HotHourUsageDetail::getUserId, parseLong(record.getUserId()))
                        .eq(HotHourUsageDetail::getIsDeleted, 0L))
                .stream()
                .map(HotHourUsageDetail::getUsedHours)
                .filter(Objects::nonNull)
                .mapToLong(Long::longValue)
                .sum();
        if (usedTotal >= needUseHours) {
            return;
        }

        long remainingNeed = needUseHours - usedTotal;
        List<HotHourPurchaseDetail> detailList = hotHourPurchaseDetailMapper.selectList(Wrappers.<HotHourPurchaseDetail>lambdaQuery()
                .eq(HotHourPurchaseDetail::getCompanyId, record.getCompanyId())
                .eq(HotHourPurchaseDetail::getStatus, PURCHASE_DETAIL_STATUS_EFFECTIVE)
                .gt(HotHourPurchaseDetail::getRemainingHours, 0L)
                .orderByAsc(HotHourPurchaseDetail::getId));
        if (detailList == null || detailList.isEmpty()) {
            throw new ServiceException("可用学时套餐不足，请先购买学时套餐");
        }

        Long operatorId = parseLong(record.getUserId());
        String useType = buildUseType(record.getTrainingId());
        Date now = record.getCompleteTime() != null ? record.getCompleteTime() : new Date();

        for (HotHourPurchaseDetail detail : detailList) {
            if (remainingNeed <= 0) {
                break;
            }
            Long currentRemaining = detail.getRemainingHours();
            if (currentRemaining == null || currentRemaining <= 0) {
                continue;
            }
            long currentUse = Math.min(remainingNeed, currentRemaining);
            if (!deductPurchaseDetail(detail, currentUse, now)) {
                throw new ServiceException("学时套餐已变更，请刷新后重试");
            }
            HotHourUsageDetail usageDetail = buildUsageDetail(record, detail, currentUse, useType, operatorId, now);
            if (hotHourUsageDetailMapper.insert(usageDetail) <= 0) {
                throw new ServiceException("写入学时套餐使用记录失败");
            }
            remainingNeed -= currentUse;
        }
        if (remainingNeed > 0) {
            throw new ServiceException("可用学时套餐不足，请先购买学时套餐");
        }
    }

    private boolean deductPurchaseDetail(HotHourPurchaseDetail detail, long useHours, Date useTime) {
        long beforeRemaining = detail.getRemainingHours() == null ? 0L : detail.getRemainingHours();
        long beforeUsed = detail.getUsedHours() == null ? 0L : detail.getUsedHours();
        long afterRemaining = beforeRemaining - useHours;
        long afterUsed = beforeUsed + useHours;

        HotHourPurchaseDetail update = new HotHourPurchaseDetail();
        update.setUsedHours(afterUsed);
        update.setRemainingHours(afterRemaining);
        update.setLastUseTime(useTime);
        update.setStatus(afterRemaining == 0 ? PURCHASE_DETAIL_STATUS_USED_UP : PURCHASE_DETAIL_STATUS_EFFECTIVE);
        update.setVersion(detail.getVersion() == null ? 1L : detail.getVersion() + 1);

        LambdaUpdateWrapper<HotHourPurchaseDetail> wrapper = Wrappers.<HotHourPurchaseDetail>lambdaUpdate()
                .eq(HotHourPurchaseDetail::getId, detail.getId())
                .eq(HotHourPurchaseDetail::getVersion, detail.getVersion())
                .eq(HotHourPurchaseDetail::getRemainingHours, beforeRemaining)
                .gt(HotHourPurchaseDetail::getRemainingHours, 0L);
        boolean success = hotHourPurchaseDetailMapper.update(update, wrapper) > 0;
        if (success) {
            detail.setUsedHours(afterUsed);
            detail.setRemainingHours(afterRemaining);
            detail.setLastUseTime(useTime);
            detail.setStatus(update.getStatus());
            detail.setVersion(update.getVersion());
        }
        return success;
    }

    private HotHourUsageDetail buildUsageDetail(HotTrainingCourseRecord record, HotHourPurchaseDetail detail, long usedHours,
                                                String useType, Long operatorId, Date useDate) {
        HotHourUsageDetail usageDetail = new HotHourUsageDetail();
        usageDetail.setCompanyId(record.getCompanyId());
        usageDetail.setPurchaseDetailId(detail.getId());
        usageDetail.setAccountId(detail.getAccountId());
        usageDetail.setUserId(parseLong(record.getUserId()));
        usageDetail.setUserName(StringUtils.defaultIfBlank(record.getUserName(), record.getCreateByName()));
        usageDetail.setOperatorId(operatorId);
        usageDetail.setOperatorName(StringUtils.defaultIfBlank(record.getUpdateByName(), StringUtils.defaultIfBlank(record.getUserName(), record.getCreateByName())));
        usageDetail.setUseType(useType);
        usageDetail.setPackageId(detail.getPackageId());
        usageDetail.setPackageName(detail.getPackageName());
        usageDetail.setUsedHours(usedHours);
        usageDetail.setBeforeRemainingHours(detail.getRemainingHours() + usedHours);
        usageDetail.setAfterRemainingHours(detail.getRemainingHours());
        usageDetail.setRemainingHours(detail.getRemainingHours());
        usageDetail.setUseDate(useDate);
        usageDetail.setBizOrderId(record.getId());
        usageDetail.setBizOrderNo(String.valueOf(record.getId()));
        usageDetail.setRemark("安全学习扣减学时套餐");
        return usageDetail;
    }

    private String buildUseType(Long trainingId) {
        if (trainingId == null) {
            return "日常培训";
        }
        HotTraining training = hotTrainingMapper.selectById(trainingId);
        return mapTrainingTypeLabel(training == null ? null : training.getTrainingType());
    }

    private String mapTrainingTypeLabel(String trainingType) {
        if (StringUtils.equals("pre-job", trainingType)) {
            return "岗前培训";
        }
        if (StringUtils.equals("daily", trainingType)) {
            return "日常培训";
        }
        if (StringUtils.equals("accident", trainingType)) {
            return "事故培训";
        }
        if (StringUtils.equals("special", trainingType)) {
            return "专题培训";
        }
        if (StringUtils.equals("violation", trainingType)) {
            return "违章培训";
        }
        if (StringUtils.equals("emergency", trainingType)) {
            return "应急培训";
        }
        if (StringUtils.equals("prevention", trainingType)) {
            return "双重预防学习";
        }
        if (StringUtils.equals("education", trainingType)) {
            return "再教育培训";
        }
        if (StringUtils.equals("offline", trainingType)) {
            return "线下培训";
        }
        return "其他培训";
    }

    private Long parseLong(String value) {
        if (StringUtils.isBlank(value) || !StringUtils.isNumeric(value)) {
            return null;
        }
        return Long.valueOf(value);
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(HotTrainingCourseRecord entity) {
        BigDecimal progressRate = entity.getProgressRate();
        if (progressRate != null) {
            if (progressRate.compareTo(BigDecimal.ZERO) < 0) {
                entity.setProgressRate(BigDecimal.ZERO);
            } else if (progressRate.compareTo(new BigDecimal("100")) > 0) {
                entity.setProgressRate(new BigDecimal("100"));
            }
        }
    }

    /**
     * 校验并批量删除培训人员课程学习记录信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
    }
}
