package com.usky.vpp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.usky.common.core.bean.CommonPage;
import com.usky.common.security.utils.SecurityUtils;
import com.usky.common.core.exception.BusinessException;
import com.usky.vpp.config.VppReportProperties;
import com.usky.vpp.domain.VppCustomer;
import com.usky.vpp.domain.VppDrEvaluation;
import com.usky.vpp.domain.VppDrEvent;
import com.usky.vpp.domain.VppEnergySummaryDaily;
import com.usky.vpp.domain.VppReportAuditLog;
import com.usky.vpp.domain.VppReportData;
import com.usky.vpp.domain.VppReportLog;
import com.usky.vpp.domain.VppReportRecord;
import com.usky.vpp.domain.VppReportTask;
import com.usky.vpp.domain.VppResourcePoint;
import com.usky.vpp.domain.VppSite;
import com.usky.vpp.mapper.VppCustomerMapper;
import com.usky.vpp.mapper.VppDrEvaluationMapper;
import com.usky.vpp.mapper.VppDrEventMapper;
import com.usky.vpp.mapper.VppEnergySummaryDailyMapper;
import com.usky.vpp.mapper.VppReportAuditLogMapper;
import com.usky.vpp.mapper.VppReportDataMapper;
import com.usky.vpp.mapper.VppReportLogMapper;
import com.usky.vpp.mapper.VppReportRecordMapper;
import com.usky.vpp.mapper.VppReportTaskMapper;
import com.usky.vpp.mapper.VppResourcePointMapper;
import com.usky.vpp.mapper.VppSiteMapper;
import com.usky.vpp.service.VppReportInfoService;
import com.usky.vpp.service.vo.ReportAuditLogVO;
import com.usky.vpp.service.vo.ReportAuditRequest;
import com.usky.vpp.service.vo.ReportDataSaveRequest;
import com.usky.vpp.service.vo.ReportPrefillVO;
import com.usky.vpp.service.vo.ReportRecordVO;
import com.usky.vpp.service.vo.ReportTaskCreateRequest;
import com.usky.vpp.service.vo.ReportTaskCreateResultVO;
import com.usky.vpp.service.vo.ReportTaskVO;
import com.usky.vpp.service.vo.ReportValidateResultVO;
import com.usky.vpp.util.VppAuditHelper;
import com.usky.vpp.util.VppPageHelper;
import com.usky.vpp.util.VppReportHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class VppReportInfoServiceImpl implements VppReportInfoService {

    private static final DateTimeFormatter DEADLINE_FMT = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

    @Autowired
    private VppReportTaskMapper taskMapper;
    @Autowired
    private VppReportRecordMapper recordMapper;
    @Autowired
    private VppReportDataMapper dataMapper;
    @Autowired
    private VppReportAuditLogMapper auditLogMapper;
    @Autowired
    private VppReportLogMapper reportLogMapper;
    @Autowired
    private VppCustomerMapper customerMapper;
    @Autowired
    private VppResourcePointMapper resourcePointMapper;
    @Autowired
    private VppSiteMapper siteMapper;
    @Autowired
    private VppEnergySummaryDailyMapper energySummaryDailyMapper;
    @Autowired
    private VppDrEventMapper drEventMapper;
    @Autowired
    private VppDrEvaluationMapper drEvaluationMapper;
    @Autowired
    private VppReportProperties reportProperties;

    @Override
    public CommonPage<ReportTaskVO> pageTask(Map<String, Object> params) {
        Page<VppReportTask> page = VppPageHelper.of(params);
        LambdaQueryWrapper<VppReportTask> wrapper = activeTaskWrapper();
        applyTaskFilters(wrapper, params);
        wrapper.orderByAsc(VppReportTask::getDeadline);
        Page<VppReportTask> result = taskMapper.selectPage(page, wrapper);
        List<ReportTaskVO> records = result.getRecords().stream()
                .map(this::toTaskVo)
                .collect(Collectors.toList());
        refreshOverdueTasks(result.getRecords());
        return new CommonPage<>(records, result.getTotal(), result.getCurrent(), result.getSize());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ReportTaskCreateResultVO createTask(ReportTaskCreateRequest request) {
        validateCreateTaskRequest(request);
        Integer tenantId = resolveCurrentTenantId();
        List<Long> siteIds = validateAndNormalizeSiteIds(request.getSiteIds(), tenantId);

        String taskNo = VppReportHelper.nextTaskNo();
        ensureTaskNoUnique(taskNo);

        VppReportTask task = new VppReportTask();
        task.setTaskNo(taskNo);
        task.setTaskName(request.getTaskName().trim());
        task.setReportOrg(request.getReportOrg());
        task.setReportCycle(request.getReportCycle());
        task.setReportType(request.getReportType().trim());
        task.setDeadline(request.getDeadline());
        task.setTaskStatus(VppReportHelper.TASK_STATUS_PENDING_FILL);
        task.setAssigneeId(request.getAssigneeId());
        task.setRemindDays(request.getRemindDays() != null ? request.getRemindDays() : 3);
        task.setSiteIds(VppReportHelper.toJson(siteIds));
        VppAuditHelper.fillCreate(task);
        taskMapper.insert(task);

        String reportPeriod = resolveReportPeriod(request);
        String recordNo = VppReportHelper.nextRecordNo(reportPeriod);
        ensureRecordNoUnique(recordNo);

        VppReportRecord record = new VppReportRecord();
        record.setTaskId(task.getId());
        record.setRecordNo(recordNo);
        record.setReportPeriod(reportPeriod);
        record.setSubmitMode(request.getSubmitMode() != null
                ? request.getSubmitMode() : VppReportHelper.SUBMIT_MODE_API);
        record.setRecordStatus(VppReportHelper.RECORD_STATUS_DRAFT);
        record.setRetryCount(0);
        VppAuditHelper.fillCreate(record);
        recordMapper.insert(record);

        ReportTaskCreateResultVO result = new ReportTaskCreateResultVO();
        result.setTaskId(task.getId());
        result.setTaskNo(task.getTaskNo());
        result.setRecordId(record.getId());
        result.setRecordNo(record.getRecordNo());
        result.setReportPeriod(record.getReportPeriod());
        result.setTaskStatus(task.getTaskStatus());
        result.setRecordStatus(record.getRecordStatus());
        result.setSiteIds(siteIds);
        return result;
    }

    @Override
    public ReportRecordVO getRecord(Long id) {
        VppReportRecord record = requireRecord(id);
        VppReportTask task = requireTask(record.getTaskId());
        VppReportData data = loadReportData(record.getId());
        return toRecordVo(record, task, data, loadAuditLogs(record.getId()));
    }

    @Override
    public ReportPrefillVO prefill(Long id) {
        VppReportRecord record = requireRecord(id);
        VppReportTask task = requireTask(record.getTaskId());
        return buildPrefillData(record, task);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> saveData(Long id, ReportDataSaveRequest request) {
        if (request == null || request.getFormData() == null || request.getFormData().isEmpty()) {
            throw new BusinessException("填报数据不能为空");
        }
        if (request.getIsDraft() == null) {
            throw new BusinessException("isDraft 不能为空");
        }
        VppReportRecord record = requireRecord(id);
        if (record.getRecordStatus() != VppReportHelper.RECORD_STATUS_DRAFT
                && record.getRecordStatus() != VppReportHelper.RECORD_STATUS_FAILED) {
            throw new BusinessException("当前状态不允许修改填报数据");
        }

        VppReportData data = loadReportData(record.getId());
        String formJson = VppReportHelper.toJson(request.getFormData());
        if (data == null) {
            data = new VppReportData();
            data.setRecordId(record.getId());
            data.setFormData(formJson);
            data.setIsDraft(Boolean.TRUE.equals(request.getIsDraft()) ? 1 : 0);
            VppAuditHelper.fillCreate(data);
            dataMapper.insert(data);
        } else {
            data.setFormData(formJson);
            data.setIsDraft(Boolean.TRUE.equals(request.getIsDraft()) ? 1 : 0);
            data.setValidationErrors(null);
            VppAuditHelper.fillUpdate(data);
            dataMapper.updateById(data);
        }

        if (record.getRecordStatus() == VppReportHelper.RECORD_STATUS_FAILED) {
            record.setRecordStatus(VppReportHelper.RECORD_STATUS_DRAFT);
            VppAuditHelper.fillUpdate(record);
            recordMapper.updateById(record);
        }

        Map<String, Object> result = new HashMap<>(2);
        result.put("recordId", record.getId());
        result.put("isDraft", request.getIsDraft());
        return result;
    }

    @Override
    public ReportValidateResultVO validateData(Long id) {
        VppReportRecord record = requireRecord(id);
        VppReportTask task = requireTask(record.getTaskId());
        VppReportData data = requireReportData(record.getId());
        Map<String, Object> formData = VppReportHelper.parseFormData(data.getFormData());
        Integer tenantId = resolveTenantId(record, task);
        List<Long> siteIds = resolveScopedSiteIds(task);
        int systemPvCount = countResourceType("PV", tenantId, siteIds);
        ReportValidateResultVO result = VppReportHelper.validateFormData(formData, systemPvCount, true);
        data.setValidationErrors(VppReportHelper.toJson(result.getErrors()));
        VppAuditHelper.fillUpdate(data);
        dataMapper.updateById(data);
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> submitAudit(Long id) {
        VppReportRecord record = requireRecord(id);
        if (record.getRecordStatus() != VppReportHelper.RECORD_STATUS_DRAFT
                && record.getRecordStatus() != VppReportHelper.RECORD_STATUS_FAILED) {
            throw new BusinessException("仅草稿或失败状态可提交审核");
        }
        VppReportData data = requireReportData(record.getId());
        if (data.getIsDraft() != null && data.getIsDraft() == 1) {
            throw new BusinessException("请先定稿后再提交审核");
        }
        ReportValidateResultVO validation = validateData(id);
        if (!validation.isValid()) {
            throw new BusinessException("填报数据校验未通过，请先修正");
        }

        record.setRecordStatus(VppReportHelper.RECORD_STATUS_PENDING_AUDIT);
        VppAuditHelper.fillUpdate(record);
        recordMapper.updateById(record);

        VppReportTask task = requireTask(record.getTaskId());
        if (task.getTaskStatus() == VppReportHelper.TASK_STATUS_PENDING_FILL) {
            task.setTaskStatus(VppReportHelper.TASK_STATUS_PENDING_AUDIT);
            VppAuditHelper.fillUpdate(task);
            taskMapper.updateById(task);
        }

        saveAuditLog(record.getId(), 1, VppReportHelper.AUDIT_ACTION_SUBMIT, "提交审核");

        Map<String, Object> result = new HashMap<>(2);
        result.put("recordId", record.getId());
        result.put("recordStatus", record.getRecordStatus());
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void audit(Long id, ReportAuditRequest request) {
        if (request == null || request.getPassed() == null) {
            throw new BusinessException("审核结果不能为空");
        }
        if (request.getAuditLevel() == null) {
            throw new BusinessException("auditLevel 不能为空");
        }
        VppReportRecord record = requireRecord(id);
        if (record.getRecordStatus() != VppReportHelper.RECORD_STATUS_PENDING_AUDIT) {
            throw new BusinessException("仅待审核状态可审核");
        }

        int action = Boolean.TRUE.equals(request.getPassed())
                ? VppReportHelper.AUDIT_ACTION_PASS
                : VppReportHelper.AUDIT_ACTION_REJECT;
        saveAuditLog(record.getId(), request.getAuditLevel(), action, request.getOpinion());

        if (Boolean.TRUE.equals(request.getPassed())) {
            return;
        }
        record.setRecordStatus(VppReportHelper.RECORD_STATUS_DRAFT);
        VppAuditHelper.fillUpdate(record);
        recordMapper.updateById(record);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ReportRecordVO submit(Long id) {
        VppReportRecord record = requireRecord(id);
        if (record.getRecordStatus() != VppReportHelper.RECORD_STATUS_PENDING_AUDIT) {
            throw new BusinessException("仅待审核且已通过审批的记录可执行报送");
        }
        if (!hasAuditPass(record.getId())) {
            throw new BusinessException("请先完成审核通过后再报送");
        }
        requireReportData(record.getId());

        int retryMax = reportProperties.getRetryMax() != null ? reportProperties.getRetryMax() : 3;
        int retryCount = record.getRetryCount() != null ? record.getRetryCount() : 0;
        if (retryCount >= retryMax) {
            throw new BusinessException("已达最大重试次数");
        }

        record.setRecordStatus(VppReportHelper.RECORD_STATUS_SUBMITTING);
        VppAuditHelper.fillUpdate(record);
        recordMapper.updateById(record);

        VppReportTask task = requireTask(record.getTaskId());
        task.setTaskStatus(VppReportHelper.TASK_STATUS_SUBMITTING);
        VppAuditHelper.fillUpdate(task);
        taskMapper.updateById(task);

        boolean success = executeExternalSubmit(record, task);
        LocalDateTime now = LocalDateTime.now();
        if (success) {
            record.setRecordStatus(VppReportHelper.RECORD_STATUS_SUCCESS);
            record.setSubmittedAt(now);
            record.setReportFileUrl("/report/files/" + record.getId() + ".xlsx");
            record.setReceiptFileUrl("/report/receipts/" + record.getId() + ".pdf");
            record.setArchivedAt(now);
            task.setTaskStatus(VppReportHelper.TASK_STATUS_COMPLETED);
        } else {
            record.setRecordStatus(VppReportHelper.RECORD_STATUS_FAILED);
            record.setRetryCount(retryCount + 1);
        }
        VppAuditHelper.fillUpdate(record);
        recordMapper.updateById(record);
        VppAuditHelper.fillUpdate(task);
        taskMapper.updateById(task);

        return toRecordVo(record, task, loadReportData(record.getId()), loadAuditLogs(record.getId()));
    }

    @Override
    public CommonPage<ReportRecordVO> pageHistory(Map<String, Object> params) {
        Page<VppReportRecord> page = VppPageHelper.of(params);
        LambdaQueryWrapper<VppReportRecord> wrapper = activeRecordWrapper();
        applyHistoryFilters(wrapper, params);
        wrapper.orderByDesc(VppReportRecord::getSubmittedAt, VppReportRecord::getCreateTime);
        Page<VppReportRecord> result = recordMapper.selectPage(page, wrapper);

        Map<Long, VppReportTask> taskMap = loadTaskMap(result.getRecords());
        List<ReportRecordVO> records = result.getRecords().stream()
                .map(record -> toHistoryVo(record, taskMap.get(record.getTaskId())))
                .collect(Collectors.toList());
        return new CommonPage<>(records, result.getTotal(), result.getCurrent(), result.getSize());
    }

    private ReportPrefillVO buildPrefillData(VppReportRecord record, VppReportTask task) {
        Integer tenantId = resolveTenantId(record, task);
        List<Long> scopedSiteIds = resolveScopedSiteIds(task);
        VppReportHelper.PeriodRange period = VppReportHelper.parseReportPeriod(record.getReportPeriod());

        List<VppSite> sites = listActiveSites(tenantId, scopedSiteIds);
        List<VppCustomer> customers = listScopedCustomers(tenantId, sites);
        List<VppResourcePoint> resources = listScopedResources(tenantId, sites);
        List<Long> resourceIds = resources.stream()
                .map(VppResourcePoint::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        List<VppEnergySummaryDaily> energyRows = listEnergyRows(period, resourceIds);
        List<VppDrEvent> drEvents = listDrEvents(period, tenantId);
        List<VppDrEvaluation> evaluations = listDrEvaluations(drEvents);
        return VppReportHelper.buildPrefill(record, task, customers, resources, sites,
                energyRows, drEvents, evaluations);
    }

    private boolean executeExternalSubmit(VppReportRecord record, VppReportTask task) {
        VppReportLog log = new VppReportLog();
        log.setReportType(task.getReportType());
        log.setRequestId(record.getRecordNo());
        log.setPayloadSize(0);
        log.setReportedAt(LocalDateTime.now());
        VppAuditHelper.fillCreate(log);
        try {
            log.setReportStatus(VppReportHelper.RECORD_STATUS_SUCCESS);
            log.setResponseCode(200);
            reportLogMapper.insert(log);
            return true;
        } catch (Exception ex) {
            log.setReportStatus(VppReportHelper.RECORD_STATUS_FAILED);
            log.setResponseCode(500);
            log.setErrorMessage(ex.getMessage());
            reportLogMapper.insert(log);
            return false;
        }
    }

    private boolean hasAuditPass(Long recordId) {
        LambdaQueryWrapper<VppReportAuditLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppReportAuditLog::getRecordId, recordId)
                .eq(VppReportAuditLog::getAction, VppReportHelper.AUDIT_ACTION_PASS)
                .orderByDesc(VppReportAuditLog::getCreateTime)
                .last("LIMIT 1");
        return auditLogMapper.selectOne(wrapper) != null;
    }

    private void saveAuditLog(Long recordId, int auditLevel, int action, String opinion) {
        VppReportAuditLog log = new VppReportAuditLog();
        log.setRecordId(recordId);
        log.setAuditLevel(auditLevel);
        log.setAction(action);
        log.setOpinion(opinion);
        VppAuditHelper.fillCreate(log);
        auditLogMapper.insert(log);
    }

    private void refreshOverdueTasks(List<VppReportTask> tasks) {
        LocalDateTime now = LocalDateTime.now();
        for (VppReportTask task : tasks) {
            if (task.getDeadline() != null
                    && task.getDeadline().isBefore(now)
                    && task.getTaskStatus() != null
                    && task.getTaskStatus() < VppReportHelper.TASK_STATUS_COMPLETED) {
                task.setTaskStatus(VppReportHelper.TASK_STATUS_OVERDUE);
                VppAuditHelper.fillUpdate(task);
                taskMapper.updateById(task);
            }
        }
    }

    private ReportTaskVO toTaskVo(VppReportTask task) {
        ReportTaskVO vo = new ReportTaskVO();
        vo.setId(task.getId());
        vo.setTaskNo(task.getTaskNo());
        vo.setTaskName(task.getTaskName());
        vo.setReportOrg(task.getReportOrg());
        vo.setReportOrgName(VppReportHelper.resolveReportOrgName(task.getReportOrg()));
        vo.setReportCycle(task.getReportCycle());
        vo.setReportType(task.getReportType());
        vo.setDeadline(task.getDeadline());
        vo.setTaskStatus(task.getTaskStatus());
        vo.setAssigneeId(task.getAssigneeId());
        vo.setAssigneeName(task.getCreatedBy());
        vo.setRemindDays(task.getRemindDays());
        vo.setSiteIds(resolveScopedSiteIds(task));
        return vo;
    }

    private ReportRecordVO toRecordVo(VppReportRecord record,
                                      VppReportTask task,
                                      VppReportData data,
                                      List<VppReportAuditLog> auditLogs) {
        ReportRecordVO vo = new ReportRecordVO();
        vo.setId(record.getId());
        vo.setTaskId(record.getTaskId());
        vo.setTaskName(task.getTaskName());
        vo.setRecordNo(record.getRecordNo());
        vo.setReportPeriod(record.getReportPeriod());
        vo.setReportOrg(task.getReportOrg());
        vo.setReportOrgName(VppReportHelper.resolveReportOrgName(task.getReportOrg()));
        vo.setSubmitMode(record.getSubmitMode());
        vo.setSiteIds(resolveScopedSiteIds(task));
        vo.setRecordStatus(record.getRecordStatus());
        vo.setReportFileUrl(record.getReportFileUrl());
        vo.setReceiptFileUrl(record.getReceiptFileUrl());
        vo.setRetryCount(record.getRetryCount());
        vo.setSubmittedAt(record.getSubmittedAt());
        if (data != null) {
            vo.setFormData(VppReportHelper.parseFormData(data.getFormData()));
            vo.setIsDraft(data.getIsDraft() != null && data.getIsDraft() == 1);
        } else {
            vo.setIsDraft(true);
        }
        vo.setAuditLogs(auditLogs.stream().map(this::toAuditLogVo).collect(Collectors.toList()));
        return vo;
    }

    private ReportRecordVO toHistoryVo(VppReportRecord record, VppReportTask task) {
        ReportRecordVO vo = new ReportRecordVO();
        vo.setId(record.getId());
        vo.setRecordNo(record.getRecordNo());
        vo.setReportPeriod(record.getReportPeriod());
        vo.setRecordStatus(record.getRecordStatus());
        vo.setSubmittedAt(record.getSubmittedAt());
        vo.setReceiptFileUrl(record.getReceiptFileUrl());
        if (task != null) {
            vo.setTaskName(task.getTaskName());
            vo.setReportOrg(task.getReportOrg());
            vo.setReportOrgName(VppReportHelper.resolveReportOrgName(task.getReportOrg()));
        }
        return vo;
    }

    private ReportAuditLogVO toAuditLogVo(VppReportAuditLog log) {
        ReportAuditLogVO vo = new ReportAuditLogVO();
        vo.setId(log.getId());
        vo.setAuditLevel(log.getAuditLevel());
        vo.setAction(log.getAction());
        vo.setOpinion(log.getOpinion());
        vo.setOperatorName(log.getCreatedBy());
        vo.setOperatedAt(log.getCreateTime());
        return vo;
    }

    private Map<Long, VppReportTask> loadTaskMap(List<VppReportRecord> records) {
        if (records == null || records.isEmpty()) {
            return Collections.emptyMap();
        }
        List<Long> taskIds = records.stream()
                .map(VppReportRecord::getTaskId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (taskIds.isEmpty()) {
            return Collections.emptyMap();
        }
        LambdaQueryWrapper<VppReportTask> wrapper = activeTaskWrapper();
        wrapper.in(VppReportTask::getId, taskIds);
        return taskMapper.selectList(wrapper).stream()
                .collect(Collectors.toMap(VppReportTask::getId, task -> task, (a, b) -> a));
    }

    private List<VppReportAuditLog> loadAuditLogs(Long recordId) {
        LambdaQueryWrapper<VppReportAuditLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppReportAuditLog::getRecordId, recordId)
                .orderByAsc(VppReportAuditLog::getCreateTime);
        return auditLogMapper.selectList(wrapper);
    }

    private VppReportData loadReportData(Long recordId) {
        LambdaQueryWrapper<VppReportData> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppReportData::getRecordId, recordId)
                .eq(VppReportData::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .last("LIMIT 1");
        return dataMapper.selectOne(wrapper);
    }

    private VppReportData requireReportData(Long recordId) {
        VppReportData data = loadReportData(recordId);
        if (data == null) {
            throw new BusinessException("请先保存填报数据");
        }
        return data;
    }

    private VppReportRecord requireRecord(Long id) {
        if (id == null) {
            throw new BusinessException("报送记录ID不能为空");
        }
        LambdaQueryWrapper<VppReportRecord> wrapper = activeRecordWrapper();
        wrapper.eq(VppReportRecord::getId, id);
        VppReportRecord record = recordMapper.selectOne(wrapper);
        if (record == null) {
            throw new BusinessException("报送记录不存在");
        }
        return record;
    }

    private VppReportTask requireTask(Long taskId) {
        if (taskId == null) {
            throw new BusinessException("报送任务不存在");
        }
        LambdaQueryWrapper<VppReportTask> wrapper = activeTaskWrapper();
        wrapper.eq(VppReportTask::getId, taskId);
        VppReportTask task = taskMapper.selectOne(wrapper);
        if (task == null) {
            throw new BusinessException("报送任务不存在");
        }
        return task;
    }

    private LambdaQueryWrapper<VppReportTask> activeTaskWrapper() {
        LambdaQueryWrapper<VppReportTask> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppReportTask::getDeleteFlag, VppAuditHelper.NOT_DELETED);
        return wrapper;
    }

    private LambdaQueryWrapper<VppReportRecord> activeRecordWrapper() {
        LambdaQueryWrapper<VppReportRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppReportRecord::getDeleteFlag, VppAuditHelper.NOT_DELETED);
        return wrapper;
    }

    private void applyTaskFilters(LambdaQueryWrapper<VppReportTask> wrapper, Map<String, Object> params) {
        if (params == null) {
            return;
        }
        Integer taskStatus = parseInt(params.get("taskStatus"));
        if (taskStatus != null) {
            wrapper.eq(VppReportTask::getTaskStatus, taskStatus);
        }
        Integer reportOrg = parseInt(params.get("reportOrg"));
        if (reportOrg != null) {
            wrapper.eq(VppReportTask::getReportOrg, reportOrg);
        }
        Integer reportCycle = parseInt(params.get("reportCycle"));
        if (reportCycle != null) {
            wrapper.eq(VppReportTask::getReportCycle, reportCycle);
        }
        LocalDateTime deadlineStart = parseDateTime(params.get("deadlineStart"));
        if (deadlineStart != null) {
            wrapper.ge(VppReportTask::getDeadline, deadlineStart);
        }
        LocalDateTime deadlineEnd = parseDateTime(params.get("deadlineEnd"));
        if (deadlineEnd != null) {
            wrapper.le(VppReportTask::getDeadline, deadlineEnd);
        }
    }

    private void applyHistoryFilters(LambdaQueryWrapper<VppReportRecord> wrapper, Map<String, Object> params) {
        if (params == null) {
            return;
        }
        Integer recordStatus = parseInt(params.get("recordStatus"));
        if (recordStatus != null) {
            wrapper.eq(VppReportRecord::getRecordStatus, recordStatus);
        }
        String reportPeriod = parseString(params.get("reportPeriod"));
        if (StringUtils.hasText(reportPeriod)) {
            wrapper.eq(VppReportRecord::getReportPeriod, reportPeriod);
        }
        Integer reportOrg = parseInt(params.get("reportOrg"));
        if (reportOrg != null) {
            List<Long> taskIds = listTaskIdsByOrg(reportOrg);
            if (taskIds.isEmpty()) {
                wrapper.eq(VppReportRecord::getTaskId, -1L);
            } else {
                wrapper.in(VppReportRecord::getTaskId, taskIds);
            }
        }
    }

    private List<Long> listTaskIdsByOrg(Integer reportOrg) {
        LambdaQueryWrapper<VppReportTask> wrapper = activeTaskWrapper();
        wrapper.eq(VppReportTask::getReportOrg, reportOrg)
                .select(VppReportTask::getId);
        return taskMapper.selectList(wrapper).stream()
                .map(VppReportTask::getId)
                .collect(Collectors.toList());
    }

    private Integer resolveTenantId(VppReportRecord record, VppReportTask task) {
        if (record != null && record.getTenantId() != null) {
            return record.getTenantId();
        }
        if (task != null && task.getTenantId() != null) {
            return task.getTenantId();
        }
        try {
            return SecurityUtils.getTenantId();
        } catch (Exception ex) {
            return null;
        }
    }

    private List<VppCustomer> listScopedCustomers(Integer tenantId, List<VppSite> sites) {
        Set<Long> customerIds = sites.stream()
                .map(VppSite::getCustomerId)
                .filter(Objects::nonNull)
                .collect(Collectors.toCollection(HashSet::new));
        if (!customerIds.isEmpty()) {
            LambdaQueryWrapper<VppCustomer> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                    .in(VppCustomer::getId, customerIds);
            applyTenantFilter(wrapper, tenantId, VppCustomer::getTenantId);
            return customerMapper.selectList(wrapper);
        }
        return listActiveCustomers(tenantId);
    }

    private List<VppCustomer> listActiveCustomers(Integer tenantId) {
        LambdaQueryWrapper<VppCustomer> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED);
        applyTenantFilter(wrapper, tenantId, VppCustomer::getTenantId);
        return customerMapper.selectList(wrapper);
    }

    private List<VppResourcePoint> listScopedResources(Integer tenantId, List<VppSite> sites) {
        List<Long> siteIds = sites.stream()
                .map(VppSite::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (siteIds.isEmpty()) {
            return listActiveResources(tenantId);
        }
        LambdaQueryWrapper<VppResourcePoint> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .in(VppResourcePoint::getSiteId, siteIds);
        applyTenantFilter(wrapper, tenantId, VppResourcePoint::getTenantId);
        return resourcePointMapper.selectList(wrapper);
    }

    private List<VppResourcePoint> listActiveResources(Integer tenantId) {
        LambdaQueryWrapper<VppResourcePoint> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED);
        applyTenantFilter(wrapper, tenantId, VppResourcePoint::getTenantId);
        return resourcePointMapper.selectList(wrapper);
    }

    private List<VppSite> listActiveSites(Integer tenantId, List<Long> scopedSiteIds) {
        LambdaQueryWrapper<VppSite> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppSite::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .orderByAsc(VppSite::getSiteName);
        applyTenantFilter(wrapper, tenantId, VppSite::getTenantId);
        if (scopedSiteIds != null && !scopedSiteIds.isEmpty()) {
            wrapper.in(VppSite::getId, scopedSiteIds);
        }
        return siteMapper.selectList(wrapper);
    }

    private int countResourceType(String resourceType, Integer tenantId, List<Long> siteIds) {
        LambdaQueryWrapper<VppResourcePoint> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .eq(VppResourcePoint::getResourceType, resourceType);
        applyTenantFilter(wrapper, tenantId, VppResourcePoint::getTenantId);
        if (siteIds != null && !siteIds.isEmpty()) {
            wrapper.in(VppResourcePoint::getSiteId, siteIds);
        }
        return resourcePointMapper.selectCount(wrapper).intValue();
    }

    private List<VppEnergySummaryDaily> listEnergyRows(VppReportHelper.PeriodRange period, List<Long> resourceIds) {
        if (period == null) {
            return Collections.emptyList();
        }
        LambdaQueryWrapper<VppEnergySummaryDaily> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppEnergySummaryDaily::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .ge(VppEnergySummaryDaily::getSummaryDate, period.getStart())
                .le(VppEnergySummaryDaily::getSummaryDate, period.getEnd());
        if (resourceIds != null && !resourceIds.isEmpty()) {
            wrapper.in(VppEnergySummaryDaily::getResourceId, resourceIds);
        } else {
            return Collections.emptyList();
        }
        return energySummaryDailyMapper.selectList(wrapper);
    }

    private List<VppDrEvent> listDrEvents(VppReportHelper.PeriodRange period, Integer tenantId) {
        if (period == null) {
            return Collections.emptyList();
        }
        LocalDateTime start = period.getStart().atStartOfDay();
        LocalDateTime end = period.getEnd().atTime(23, 59, 59);
        LambdaQueryWrapper<VppDrEvent> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .ge(VppDrEvent::getStartTime, start)
                .le(VppDrEvent::getStartTime, end);
        applyTenantFilter(wrapper, tenantId, VppDrEvent::getTenantId);
        return drEventMapper.selectList(wrapper);
    }

    private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Integer tenantId,
                                       com.baomidou.mybatisplus.core.toolkit.support.SFunction<T, ?> column) {
        if (tenantId != null) {
            wrapper.eq(column, tenantId);
        }
    }

    private List<VppDrEvaluation> listDrEvaluations(List<VppDrEvent> events) {
        if (events == null || events.isEmpty()) {
            return Collections.emptyList();
        }
        List<Long> eventIds = events.stream().map(VppDrEvent::getId).collect(Collectors.toList());
        LambdaQueryWrapper<VppDrEvaluation> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VppDrEvaluation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .in(VppDrEvaluation::getEventId, eventIds);
        return drEvaluationMapper.selectList(wrapper);
    }

    private Integer parseInt(Object value) {
        if (value == null || !StringUtils.hasText(value.toString())) {
            return null;
        }
        return Integer.parseInt(value.toString());
    }

    private String parseString(Object value) {
        return value == null ? null : value.toString();
    }

    private LocalDateTime parseDateTime(Object value) {
        if (value == null || !StringUtils.hasText(value.toString())) {
            return null;
        }
        String text = value.toString();
        try {
            return LocalDateTime.parse(text, DEADLINE_FMT);
        } catch (DateTimeParseException ex) {
            try {
                return LocalDate.parse(text).atStartOfDay();
            } catch (DateTimeParseException ignored) {
                return null;
            }
        }
    }

    private void validateCreateTaskRequest(ReportTaskCreateRequest request) {
        if (request == null) {
            throw new BusinessException("请求体不能为空");
        }
        if (!StringUtils.hasText(request.getTaskName())) {
            throw new BusinessException("任务名称不能为空");
        }
        if (request.getReportOrg() == null || request.getReportOrg() < 1 || request.getReportOrg() > 4) {
            throw new BusinessException("报送机构无效，取值 1~4");
        }
        if (request.getReportCycle() == null || request.getReportCycle() < 1 || request.getReportCycle() > 3) {
            throw new BusinessException("报送周期无效，取值 1~3");
        }
        if (!StringUtils.hasText(request.getReportType())) {
            throw new BusinessException("报表类型编码不能为空");
        }
        if (request.getDeadline() == null) {
            throw new BusinessException("截止日期不能为空");
        }
        if (request.getSubmitMode() != null
                && request.getSubmitMode() != VppReportHelper.SUBMIT_MODE_API
                && request.getSubmitMode() != VppReportHelper.SUBMIT_MODE_MANUAL) {
            throw new BusinessException("报送方式无效，取值 1 或 2");
        }
        String reportPeriod = resolveReportPeriod(request);
        if (request.getReportCycle() == 1 && !reportPeriod.matches("\\d{4}-\\d{2}")) {
            throw new BusinessException("月报报送周期须为 YYYY-MM 格式");
        }
        if (request.getReportCycle() == 2 && !reportPeriod.matches("\\d{4}")) {
            throw new BusinessException("年报报送周期须为 YYYY 格式");
        }
        if (request.getSiteIds() == null || request.getSiteIds().isEmpty()) {
            throw new BusinessException("请至少选择一个关联站点");
        }
    }

    private Integer resolveCurrentTenantId() {
        try {
            return SecurityUtils.getTenantId();
        } catch (Exception ex) {
            return null;
        }
    }

    private List<Long> resolveScopedSiteIds(VppReportTask task) {
        if (task == null) {
            return Collections.emptyList();
        }
        return VppReportHelper.parseSiteIds(task.getSiteIds());
    }

    private List<Long> validateAndNormalizeSiteIds(List<Long> siteIds, Integer tenantId) {
        if (siteIds == null || siteIds.isEmpty()) {
            throw new BusinessException("请至少选择一个关联站点");
        }
        List<Long> distinct = siteIds.stream()
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (distinct.isEmpty()) {
            throw new BusinessException("请至少选择一个关联站点");
        }
        List<VppSite> sites = siteMapper.selectBatchIds(distinct);
        Map<Long, VppSite> siteMap = sites.stream()
                .filter(site -> site.getDeleteFlag() == null
                        || Objects.equals(site.getDeleteFlag(), VppAuditHelper.NOT_DELETED))
                .collect(Collectors.toMap(VppSite::getId, site -> site, (left, right) -> left));
        for (Long siteId : distinct) {
            VppSite site = siteMap.get(siteId);
            if (site == null) {
                throw new BusinessException("站点不存在或已删除: " + siteId);
            }
            if (tenantId != null && site.getTenantId() != null && !tenantId.equals(site.getTenantId())) {
                throw new BusinessException("站点不属于当前租户: " + siteId);
            }
        }
        return distinct;
    }

    private String resolveReportPeriod(ReportTaskCreateRequest request) {
        if (StringUtils.hasText(request.getReportPeriod())) {
            return request.getReportPeriod().trim();
        }
        if (request.getDeadline() != null) {
            if (request.getReportCycle() != null && request.getReportCycle() == 2) {
                return String.valueOf(request.getDeadline().getYear());
            }
            return String.format("%04d-%02d", request.getDeadline().getYear(), request.getDeadline().getMonthValue());
        }
        throw new BusinessException("报送周期标识不能为空");
    }

    private void ensureTaskNoUnique(String taskNo) {
        LambdaQueryWrapper<VppReportTask> wrapper = activeTaskWrapper();
        wrapper.eq(VppReportTask::getTaskNo, taskNo);
        if (taskMapper.selectCount(wrapper) > 0) {
            throw new BusinessException("任务编号冲突，请重试");
        }
    }

    private void ensureRecordNoUnique(String recordNo) {
        LambdaQueryWrapper<VppReportRecord> wrapper = activeRecordWrapper();
        wrapper.eq(VppReportRecord::getRecordNo, recordNo);
        if (recordMapper.selectCount(wrapper) > 0) {
            throw new BusinessException("报送记录编号冲突，请重试");
        }
    }
}
