package org.dromara.rfid.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
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.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.rfid.helper.RfidReadRecordTableHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.dromara.rfid.domain.bo.RfidReadRecordBo;
import org.dromara.rfid.domain.vo.RfidReadRecordVo;
import org.dromara.rfid.domain.RfidReadRecord;
import org.dromara.rfid.mapper.RfidReadRecordMapper;
import org.dromara.rfid.service.IRfidReadRecordService;

import java.util.Date;
import java.util.List;
import java.util.Collection;

/**
 * 读取记录Service业务层处理
 * <p>
 * 支持按日期分表，表名格式：rfid_read_record_yyyyMMdd
 * </p>
 *
 * @author zch
 * @date 2025-11-25
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class RfidReadRecordServiceImpl implements IRfidReadRecordService {

    private final RfidReadRecordMapper baseMapper;

    /**
     * 查询读取记录
     *
     * @param id        主键
     * @param queryDate 查询日期（用于定位分表）
     * @return 读取记录
     */
    @Override
    public RfidReadRecordVo queryById(Long id, Date queryDate) {
        String tableName = RfidReadRecordTableHelper.getTableName(queryDate);
        return baseMapper.selectCustomRfidReadRecordVoById(tableName, id);
    }

    /**
     * 分页查询读取记录列表
     * <p>
     * 根据 beginRecordTime 和 endRecordTime 确定查询的分表范围，
     * 只查询实际存在的分表
     * </p>
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 读取记录分页列表
     */
    @Override
    public TableDataInfo<RfidReadRecordVo> queryPageList(RfidReadRecordBo bo, PageQuery pageQuery) {
        // 获取实际存在的分表列表
        List<String> tableNames = RfidReadRecordTableHelper.getExistingTableNames(
            bo.getBeginRecordTime(), bo.getEndRecordTime());

        // 没有存在的分表，返回空数据
        if (CollUtil.isEmpty(tableNames)) {
            return TableDataInfo.build(new Page<>());
        }

        LambdaQueryWrapper<RfidReadRecord> lqw = buildQueryWrapper(bo);
        Page<RfidReadRecordVo> result = tableNames.size() == 1
            ? baseMapper.selectCustomRfidReadRecordVoPage(tableNames.get(0), pageQuery.build(), lqw)
            : baseMapper.selectCustomRfidReadRecordVoPageMultiTable(tableNames, pageQuery.build(), lqw);

        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的读取记录列表
     * <p>
     * 只查询实际存在的分表。
     * 支持采样查询：当 samplingInterval > 1 时，每 N 分钟取一条代表数据
     * </p>
     *
     * @param bo 查询条件
     * @return 读取记录列表
     */
    @Override
    public List<RfidReadRecordVo> queryList(RfidReadRecordBo bo) {
        // 获取实际存在的分表列表
        List<String> tableNames = RfidReadRecordTableHelper.getExistingTableNames(
            bo.getBeginRecordTime(), bo.getEndRecordTime());

        if (CollUtil.isEmpty(tableNames)) {
            return List.of();
        }

        LambdaQueryWrapper<RfidReadRecord> lqw = buildQueryWrapper(bo);

        // 判断是否使用采样查询（无索引依赖，大数据量优化）
        Integer samplingInterval = bo.getSamplingInterval();
        if (samplingInterval != null && samplingInterval > 1) {
            // 采样查询：每 N 分钟取一条代表数据
            // tableNames.size() == 1 表示只涉及一张分表，直接调用单表采样SQL；
            // 否则说明跨多天/多张分表，调用多表采样SQL进行 UNION ALL 聚合。
            return tableNames.size() == 1
                ? baseMapper.selectWithSampling(tableNames.get(0), samplingInterval, lqw)
                : baseMapper.selectWithSamplingMultiTable(tableNames, samplingInterval, lqw);
        }

        // 普通查询
        return tableNames.size() == 1
            ? baseMapper.selectCustomRfidReadRecordVoList(tableNames.get(0), lqw)
            : baseMapper.selectCustomRfidReadRecordVoListMultiTable(tableNames, lqw);
    }

    /**
     * 构建查询条件
     * <p>
     * 注意：排序在 Mapper XML 中控制，这里只构建 WHERE 条件
     * 条件顺序：优先使用索引字段（device_id, record_time）
     * </p>
     */
    private LambdaQueryWrapper<RfidReadRecord> buildQueryWrapper(RfidReadRecordBo bo) {
        LambdaQueryWrapper<RfidReadRecord> lqw = Wrappers.lambdaQuery();
        // 优先条件：设备ID（索引字段）
        lqw.eq(bo.getDeviceId() != null, RfidReadRecord::getDeviceId, bo.getDeviceId());
        // 时间范围查询（索引字段），使用 BETWEEN 更高效
        if (bo.getBeginRecordTime() != null && bo.getEndRecordTime() != null) {
            lqw.between(RfidReadRecord::getRecordTime, bo.getBeginRecordTime(), bo.getEndRecordTime());
        } else {
            lqw.ge(bo.getBeginRecordTime() != null, RfidReadRecord::getRecordTime, bo.getBeginRecordTime());
            lqw.le(bo.getEndRecordTime() != null, RfidReadRecord::getRecordTime, bo.getEndRecordTime());
        }
        // 其他条件
        lqw.eq(StringUtils.isNotBlank(bo.getReadStatus()), RfidReadRecord::getReadStatus, bo.getReadStatus());
        lqw.eq(StringUtils.isNotBlank(bo.getBarcode()), RfidReadRecord::getBarcode, bo.getBarcode());
        lqw.eq(StringUtils.isNotBlank(bo.getAlarmFlag()), RfidReadRecord::getAlarmFlag, bo.getAlarmFlag());
        lqw.eq(StringUtils.isNotBlank(bo.getAlarmLevel()), RfidReadRecord::getAlarmLevel, bo.getAlarmLevel());
        lqw.eq(StringUtils.isNotBlank(bo.getAlarmType()), RfidReadRecord::getAlarmType, bo.getAlarmType());
        lqw.eq(StringUtils.isNotBlank(bo.getAlarmAction()), RfidReadRecord::getAlarmAction, bo.getAlarmAction());
        return lqw;
    }

    /**
     * 新增读取记录
     * <p>
     * 根据 recordTime 自动路由到对应日期的分表
     * </p>
     *
     * @param bo 读取记录
     * @return 是否新增成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean insertByBo(RfidReadRecordBo bo) {
        RfidReadRecord add = MapstructUtils.convert(bo, RfidReadRecord.class);
        validEntityBeforeSave(add);

        // 自定义 SQL 不会自动生成 ID，需要手动生成雪花 ID
        if (add.getId() == null) {
            add.setId(IdUtil.getSnowflakeNextId());
        }

        // 根据 recordTime 路由到对应分表
        String tableName = RfidReadRecordTableHelper.getTableName(add.getRecordTime());
        boolean flag = baseMapper.insertRfidReadRecord(tableName, add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改读取记录
     * <p>
     * 根据 recordTime 定位分表进行更新
     * </p>
     *
     * @param bo 读取记录
     * @return 是否修改成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateByBo(RfidReadRecordBo bo) {
        RfidReadRecord update = MapstructUtils.convert(bo, RfidReadRecord.class);
        validEntityBeforeSave(update);

        // 根据 recordTime 路由到对应分表
        String tableName = RfidReadRecordTableHelper.getTableName(update.getRecordTime());
        return baseMapper.updateRfidReadRecordById(tableName, update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(RfidReadRecord entity) {
        if (entity == null) {
            throw new ServiceException("读取记录不能为空");
        }

        if (entity.getBarcode() != null) {
            entity.setBarcode(entity.getBarcode().trim());
        }

        if ("1".equals(entity.getReadStatus()) && StringUtils.isBlank(entity.getBarcode())) {
            throw new ServiceException("读取成功时条码信息不能为空");
        }
    }

    /**
     * 校验并批量删除读取记录信息
     *
     * @param ids       待删除的主键集合
     * @param queryDate 查询日期（用于定位分表）
     * @param isValid   是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteWithValidByIds(Collection<Long> ids, Date queryDate, Boolean isValid) {
        if (CollUtil.isEmpty(ids)) {
            return Boolean.TRUE;
        }
        String tableName = RfidReadRecordTableHelper.getTableName(queryDate);
        return baseMapper.deleteCustomRfidReadRecordByIds(tableName, ids) > 0;
    }
}
