package com.centreproject.system.service.revenueAttendance.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.centreproject.common.constant.GuestConstants;
import com.centreproject.common.constant.KeyConstants;
import com.centreproject.common.constant.KpiIndexConstants;
import com.centreproject.common.enums.DataSourceSystem;
import com.centreproject.common.enums.SyncStatus;
import com.centreproject.common.utils.DateUtils;
import com.centreproject.common.utils.StringUtils;
import com.centreproject.common.utils.TableNameUtils;
import com.centreproject.system.domain.kpi.Park;
import com.centreproject.system.domain.kpi.revenueAttendance.KpiRevenue;
import com.centreproject.system.domain.kpi.revenueAttendance.KpiRevenueAttendance;
import com.centreproject.system.domain.sync.kpi.KpiDailyParkRevenue;
import com.centreproject.system.domain.sync.ods.OdsAttendance;
import com.centreproject.system.domain.sync.ods.OdsRevenue;
import com.centreproject.system.domain.vo.*;
import com.centreproject.system.mapper.kpi.KpiRevenueAttendanceMapper;
import com.centreproject.system.mapper.kpi.KpiRevenueMapper;
import com.centreproject.system.mapper.kpi.ParkMapper;
import com.centreproject.system.mapper.sync.ods.OdsAttendanceMapper;
import com.centreproject.system.mapper.sync.ods.OdsRevenueMapper;
import com.centreproject.system.service.job.factory.SyncRuleHandlerFactory;
import com.centreproject.system.service.revenueAttendance.IKpiRevenueAttendanceService;
import com.centreproject.system.service.sync.IDataSyncLogService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Revenue&Attendance
 * @author zhonglei.xu
 * @description 实现KPI-收入与出勤率业务逻辑
 */
@Service
@RequiredArgsConstructor
public class KpiRevenueAttendanceServiceImpl  extends ServiceImpl<KpiRevenueAttendanceMapper, KpiRevenueAttendance>
        implements IKpiRevenueAttendanceService {


    private final Logger logger = LoggerFactory.getLogger(KpiRevenueAttendanceServiceImpl.class);
    private static final ZoneId RIYADH_ZONE = ZoneId.of("Asia/Riyadh");
    private final Executor virtualThreadExecutor;
    private final ParkMapper parkMapper;
    private final IDataSyncLogService dataSyncService;
    private final TransactionTemplate transactionTemplate;
    private final OdsAttendanceMapper odsAttendanceMapper;
    private final OdsRevenueMapper odsRevenueMapper;
    private final KpiRevenueMapper kpiRevenueMapper;
    // 规则工厂
    private final SyncRuleHandlerFactory ruleHandlerFactory;
    @Autowired
    private KpiRevenueAttendanceMapper mapper;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    /**
     * Revenue Attendance Kpi (Revenue Tracking last 60 days)
     * @return Revenue Tracking last 60 days
     */
    @Override
    public RevenueTrackingVo getRevenueTracking() {
        String redisKey = KeyConstants.REVENUE_TRACKING + DateUtils.getRiDate() + GuestConstants.LATEST_60_DAYS;

        // 1. 查缓存
        RevenueTrackingVo cached = (RevenueTrackingVo) redisTemplate.opsForValue().get(redisKey);
        if (null!=cached) {
            return cached;
        }
        RevenueTrackingVo vo=mapper.getRevenueTrackingVoByLast60DaysOfSum();
        if(ObjectUtils.isNotEmpty(vo)){
            List<RevenueTrackingDetailVo> detailVo = mapper.getRevenueTrackingVoByLast60DaysOfDetail();
            vo.setDay(DateUtils.getShortYearDate());
            vo.setAttendance(detailVo);
            // 规则处理
            ruleProcessForRevenueTracking(vo);
            ruleHandlerFactory.refreshRuleCache(KeyConstants.REVENUE_ATTENDANCE);
            redisTemplate.opsForValue().set(redisKey, vo, 1, TimeUnit.HOURS);
        }
        return vo;
    }

    /**
     * Revenue Tracking Kpi (Revenue Tracking last 60 days)
     * @param vo
     */
    public void ruleProcessForRevenueTracking(RevenueTrackingVo vo){
        Integer attendanceTotal=null;
        if(CollectionUtils.isNotEmpty(vo.getAttendance())) {
            for (RevenueTrackingDetailVo detailVo : vo.getAttendance()) {
                Integer attendance = ruleHandlerFactory.applyDefaultValue(KeyConstants.REVENUE_ATTENDANCE, KpiIndexConstants.ATTENDANCE, detailVo.getAttendance(), Integer.class);
                ruleHandlerFactory.checkThreshold(KeyConstants.REVENUE_ATTENDANCE, KpiIndexConstants.ATTENDANCE, attendanceTotal);
                if(null!=attendance){
                    attendanceTotal=attendanceTotal==null?attendance:attendanceTotal+attendance;
                }
            }
        }

        vo.setAttendanceTotal(attendanceTotal);
    }
    /**
     * Visitor Segmentation Kpi (Visitor Segmentation last 60 days)
     * @return
     */
    @Override
    public VisitorSegmentationVo getVisitorSegmentation() {
        String redisKey = KeyConstants.ATTENDANCE_VISITOR_SEGMENTATION + DateUtils.getRiDate() + GuestConstants.LATEST_60_DAYS;

        // 1. 查缓存
        VisitorSegmentationVo cached = (VisitorSegmentationVo) redisTemplate.opsForValue().get(redisKey);
        if (null!=cached) {
            return cached;
        }
        VisitorSegmentationVo result = new VisitorSegmentationVo();
        // 近60天的访客人群
        List<KpiRevenueAttendance> currentLists=mapper.getVisitorSegmentationByLast60DaysOfSum();
        getRuleProcessRevenue(currentLists);
        if(CollectionUtils.isNotEmpty(currentLists)){
            List<KpiRevenueAttendance> preLists=mapper.getVisitorSegmentationByLast60DaysTo120DaysOfSum();
            getRuleProcessRevenue(preLists);
            // 计算ap总量
            Integer apTotal = currentLists.stream().mapToInt(KpiRevenueAttendance::getApVisitors).sum();
            Integer vipTotal =currentLists.stream().mapToInt(KpiRevenueAttendance::getVipVisitors).sum();
            VSegmentationVo apVisitors=new VSegmentationVo();
            VSegmentationVo vipVisitors=new VSegmentationVo();
            List<ParkSegmentationVo> apList=new ArrayList<>();
            List<ParkSegmentationVo> vipList=new ArrayList<>();
            for(KpiRevenueAttendance kpi:currentLists){
                ParkSegmentationVo ap = new ParkSegmentationVo();
                ParkSegmentationVo vip = new ParkSegmentationVo();
                ap.setParkCode(kpi.getParkCode());
                ap.setCurrent(kpi.getApVisitors());
                vip.setParkCode(kpi.getParkCode());
                vip.setCurrent(kpi.getVipVisitors());
                // 计算环比的差值
                if(CollectionUtils.isNotEmpty(preLists)){
                    for(KpiRevenueAttendance pre:preLists){
                        if(pre.getParkCode().equals(kpi.getParkCode())) {
                            if(pre.getApVisitors()!=null && kpi.getApVisitors()!=null) {
                                ap.setPre(kpi.getApVisitors() - pre.getApVisitors());
                            }
                            if(pre.getVipVisitors()!=null && kpi.getVipVisitors()!=null) {
                                vip.setPre(kpi.getVipVisitors() - pre.getVipVisitors());
                            }
                            break;
                        }
                    }
                }
                apList.add(ap);
                vipList.add(vip);
            }
            apVisitors.setLists(apList);
            vipVisitors.setLists(vipList);
            apVisitors.setTotal(apTotal);
            vipVisitors.setTotal(vipTotal);
            result.setApVisitors(apVisitors);
            result.setVipVisitors(vipVisitors);
        }
        if(ObjectUtils.isNotEmpty(result)){

            // 规则处理
            ruleHandlerFactory.refreshRuleCache(KeyConstants.REVENUE_ATTENDANCE);
            redisTemplate.opsForValue().set(redisKey, result, 1, TimeUnit.HOURS);
        }
        return result;
    }

    /**
     * 规则处理
     * @param lists lists
     */
    public void getRuleProcessRevenue(List<KpiRevenueAttendance> lists){
       if(CollectionUtils.isNotEmpty(lists)){
           for(KpiRevenueAttendance attendance:lists){
               //ap
               Integer apVisitors = ruleHandlerFactory.applyDefaultValue(KeyConstants.REVENUE_ATTENDANCE, KpiIndexConstants.AP_VISITORS, attendance.getApVisitors(), Integer.class);
               ruleHandlerFactory.checkThreshold(KeyConstants.REVENUE_ATTENDANCE, KpiIndexConstants.AP_VISITORS, apVisitors);
               attendance.setApVisitors(apVisitors);
               //vip
               Integer vipVisitors = ruleHandlerFactory.applyDefaultValue(KeyConstants.REVENUE_ATTENDANCE, KpiIndexConstants.VIP_VISITORS, attendance.getVipVisitors(), Integer.class);
               ruleHandlerFactory.checkThreshold(KeyConstants.REVENUE_ATTENDANCE, KpiIndexConstants.VIP_VISITORS, attendance.getVipVisitors());
              attendance.setVipVisitors(vipVisitors);
           }
       }
    }

    @Override
    public void startSync(String traceId, long jobId) {
        // 使用虚拟线程执行同步任务
        virtualThreadExecutor.execute(() -> {
            MDC.put("traceId", traceId);
            try {
                // 同步出勤数据
                syncDataAll(traceId, jobId);
            } finally {
                MDC.clear();
            }
        });
    }

    /**
     * 同步数据考勤与收入数据
     * @param traceId traceId
     * @param jobId jobId
     */
    private void syncDataAll(String traceId, long jobId) {
        try {
            // 日志开始
            logger.info("【Revenue & Attendance】开始同步数据");
            String sourceTable = TableNameUtils.getTableNames(KeyConstants.SPLIT, OdsAttendance.class,OdsRevenue.class);
            String targetTable = TableNameUtils.getTableNames("", KpiRevenueAttendance.class);
            dataSyncService.startSync(jobId, traceId, DataSourceSystem.OMNI.getCode(), sourceTable, targetTable);

            // 沙特当前时间
            LocalDate today = LocalDate.now(RIYADH_ZONE);
            // 全量园区数据
            List<Park> parks = parkMapper.selectList(null);
            Map<String, String> parkMap = parks.stream().collect(Collectors.toMap(Park::getName, Park::getCode));
            // 1. 查询源数据
            // 1.1 出勤数据
            List<KpiRevenueAttendance> kpiRevenueAttendances = mapper.selectList(null);

            List<OdsAttendance> odsAttendancesLists;

            if(CollectionUtils.isNotEmpty(kpiRevenueAttendances)){
                odsAttendancesLists = odsAttendanceMapper
                        .selectList(new LambdaQueryWrapper<OdsAttendance>()
                                .ge(OdsAttendance::getReportHour, today));
            }else{
                odsAttendancesLists = odsAttendanceMapper.selectList(null);
            }
            // 合并数据为日数据
            List<OdsAttendance> odsAttendances = mergeHourlyToDaily(odsAttendancesLists);
            // 源数据规则处理
            getRuleProcess(odsAttendances);
            // 1.2 总收入财务数据 颗粒度为日
            Map<LocalDateTime, Map<String, BigDecimal>> revenueGeneratedMap = getRevenueGenerated(today, parkMap);
            // todo 1.3 礼宾收入 颗粒度为周无法对齐，待客户确认是否处理后再做处理

            // 2. 数据聚合
            Map<String, List<KpiRevenueAttendance>> mergedData = mergeDataAll(odsAttendances, parks,revenueGeneratedMap);
            // 3. 执行事务操作
            Integer affectedRows = transactionTemplate.execute(status -> {
                try {
                    int count = syncToTarget(mergedData, traceId);
                    status.flush();
                    return count;
                } catch (Exception e) {
                    // 设置回滚标记
                    status.setRollbackOnly();
                    throw e;
                }
            });

            // 日志结束
            dataSyncService.endSync(traceId, affectedRows, SyncStatus.SUCCESS, "Revenue & Attendance data synchronization completed successfully");
        } catch (DataAccessException e) {
            String errorMsg = String.format("数据库操作失败[表:%s]，错误: %s",
                    TableNameUtils.getTableNames(KeyConstants.SPLIT, KpiRevenueAttendance.class),
                    e.getCause() != null ? e.getCause().getMessage() : e.getMessage());
            logger.error(errorMsg, e);
            dataSyncService.endSync(traceId, 0, SyncStatus.FAILED, errorMsg);
            throw e;
        } catch (Exception e) {
            logger.error("【系统异常】", e);
            dataSyncService.endSync(traceId, 0, SyncStatus.FAILED, "系统异常");
            throw e;
        } finally {
            // 刷新规则缓存
            ruleHandlerFactory.refreshRuleCache(KeyConstants.REVENUE_ATTENDANCE);
        }
    }

    /**
     * 组装数据
     * @param odsAttendances odsAttendances
     * @param parks parks
     * @param map map
     * @return
     */
    private Map<String, List<KpiRevenueAttendance>> mergeDataAll(List<OdsAttendance> odsAttendances, List<Park> parks, Map<LocalDateTime, Map<String, BigDecimal>> map) {
        Map<String, List<OdsAttendance>> attendanceMap = odsAttendances.stream()
                .collect(Collectors.groupingBy(OdsAttendance::getPark));
        Map<String, String> parkMap = parks.stream().collect(Collectors.toMap(Park::getName, Park::getCode));
        Map<String, List<KpiRevenueAttendance>> mergedData = new HashMap<>();
        // 合并数据
        for (Park park : parks) {
            String parkCode = parkMap.get(park.getName());
            if (StringUtils.isNotEmpty(parkCode)) {
                List<OdsAttendance> lists = attendanceMap.get(park.getName());
                List<KpiRevenueAttendance> results=new ArrayList<>();
                if(CollectionUtils.isNotEmpty(lists)) {
                    for (OdsAttendance odsAttendance : lists) {
                        KpiRevenueAttendance entity = new KpiRevenueAttendance();
                        entity.setDate(odsAttendance.getReportHour());
                        entity.setParkCode(parkCode);
                        // ap出勤
                        entity.setApVisitors(odsAttendance.getAnnualPass());
                        ruleHandlerFactory.checkThreshold(KeyConstants.REVENUE_ATTENDANCE, KpiIndexConstants.AP_VISITORS, entity.getApVisitors());
                        // vip出勤
                        entity.setVipVisitors(odsAttendance.getVipAttendance());
                        ruleHandlerFactory.checkThreshold(KeyConstants.REVENUE_ATTENDANCE, KpiIndexConstants.VIP_VISITORS, entity.getVipVisitors());
                        // 总出勤
                        entity.setAttendance(odsAttendance.getTotalAttendance());
                        ruleHandlerFactory.checkThreshold(KeyConstants.REVENUE_ATTENDANCE, KpiIndexConstants.ATTENDANCE, entity.getVipVisitors());
                        // 总收入
                        BigDecimal bigDecimal = map.get(odsAttendance.getReportHour()).get(parkMap.get(odsAttendance.getPark()));
                        entity.setRevenueGenerated(bigDecimal);
                        ruleHandlerFactory.checkThreshold(KeyConstants.REVENUE_ATTENDANCE, KpiIndexConstants.ONSITE_CONCIERGE_REVENUE, entity.getVipVisitors());
                        // todo 礼宾收入
                        results.add(entity);
                    }
                }
                mergedData.put(parkCode, results);
            }
        }
        return mergedData;

    }

    /**
     * 规则处理
     * @param odsAttendances ods源数据
     */
    public void getRuleProcess(List<OdsAttendance> odsAttendances) {
        if(CollectionUtils.isNotEmpty(odsAttendances)){
            for (OdsAttendance odsAttendance : odsAttendances) {
                if(odsAttendance.getAnnualPass()==null){
                    Integer ap = ruleHandlerFactory.applyDefaultValue(KeyConstants.REVENUE_ATTENDANCE,
                            KpiIndexConstants.AP_VISITORS,
                            null,
                            Integer.class);
                    odsAttendance.setAnnualPass(ap);
                }
                if(odsAttendance.getVipAttendance()==null){
                    Integer vip = ruleHandlerFactory.applyDefaultValue(KeyConstants.REVENUE_ATTENDANCE,
                            KpiIndexConstants.VIP_VISITORS,
                            null,
                            Integer.class);
                    odsAttendance.setVipAttendance(vip);
                }
                if(odsAttendance.getTotalAttendance()==null){
                    Integer total = ruleHandlerFactory.applyDefaultValue(KeyConstants.REVENUE_ATTENDANCE,
                            KpiIndexConstants.ATTENDANCE,
                            null,
                            Integer.class);
                    odsAttendance.setTotalAttendance(total);
                }
            }
        }
    }

    /**
     * 获取总收入
     * @param today
     * @return
     */
    public Map<LocalDateTime,Map<String,BigDecimal>> getRevenueGenerated(LocalDate today,Map<String, String> parkMap){
        List<KpiRevenue> kpiRevenues = kpiRevenueMapper.selectList(null);
        Map<LocalDateTime,Map<String,BigDecimal>> map=new HashMap<>();
        List<OdsRevenue> odsRevenues;
        if(CollectionUtils.isNotEmpty(kpiRevenues)){
            // 目标表数据为空全量同步，不为空增量同步
            odsRevenues= odsRevenueMapper.selectList(new LambdaQueryWrapper<OdsRevenue>()
                    .eq(OdsRevenue::getReportDate, today));
        }else{
            odsRevenues= odsRevenueMapper.selectList(null);
        }
        getRevenue(odsRevenues, map,parkMap);

        return map;
    }

    /**
     * 数据聚合
     * @param odsRevenues
     * @param map
     */
    private void getRevenue( List<OdsRevenue> odsRevenues, Map<LocalDateTime,Map<String,BigDecimal>> map,Map<String, String> parkMap) {
        BigDecimal revenueGenerated;
        if(CollectionUtils.isNotEmpty(odsRevenues)){
            for(OdsRevenue odsRevenue:odsRevenues){
                if (ObjectUtils.isEmpty(odsRevenue) ||
                        (odsRevenue.getTotalFbRevenue() == null &&
                                odsRevenue.getTotalRetailRevenue() == null &&
                                odsRevenue.getTotalTicketRevenue() == null)) {
                    // 空值规则处理
                    revenueGenerated = ruleHandlerFactory.applyDefaultValue(
                            KeyConstants.REVENUE_ATTENDANCE,
                            KpiIndexConstants.REVENUE_GENERATED,
                            null,
                            BigDecimal.class
                    );
                } else {
                    revenueGenerated = Optional.ofNullable(odsRevenue.getTotalFbRevenue()).orElse(BigDecimal.ZERO)
                            .add(Optional.ofNullable(odsRevenue.getTotalRetailRevenue()).orElse(BigDecimal.ZERO))
                            .add(Optional.ofNullable(odsRevenue.getTotalTicketRevenue()).orElse(BigDecimal.ZERO));
                }
                LocalDateTime dateKey = odsRevenue.getReportDate().atStartOfDay();
                Map<String, BigDecimal> parkRevenueMap = map.computeIfAbsent(dateKey, k -> new HashMap<>());
                parkRevenueMap.put(parkMap.get(odsRevenue.getPark()),revenueGenerated);
                map.put(dateKey, parkRevenueMap);
            }
        }

    }

    /**
     * 将小时维度出勤数据合并为天维度
     * @param hourlyAttendances 小时维度数据列表（非空）
     * @return 天维度数据列表，按 reportDate 和 park 分组聚合
     */
    public List<OdsAttendance> mergeHourlyToDaily(List<OdsAttendance> hourlyAttendances) {
        // 1. 校验入参
        if (hourlyAttendances == null || hourlyAttendances.isEmpty()) {
            return List.of();
        }

        // 2. 按日期（reportDate）和乐园（park）分组，聚合数值字段
        Map<String, Map<LocalDate, List<OdsAttendance>>> groupedByParkAndDate = hourlyAttendances.stream()
                // 过滤无效数据（reportHour或park为空）
                .filter(attendance -> attendance.getReportHour() != null && attendance.getPark() != null)
                // 第一层分组：按乐园名称（park）
                .collect(Collectors.groupingBy(
                        OdsAttendance::getPark,
                        // 第二层分组：按日期（LocalDateTime -> LocalDate）
                        Collectors.groupingBy(attendance -> attendance.getReportHour().toLocalDate())
                ));

        // 3. 遍历分组结果，转换为 DailyAttendance 列表
        return groupedByParkAndDate.entrySet().stream()
                .flatMap(parkEntry -> {
                    String park = parkEntry.getKey();
                    Map<LocalDate, List<OdsAttendance>> dateGroup = parkEntry.getValue();

                    return dateGroup.entrySet().stream()
                            .map(dateEntry -> {
                                LocalDate reportDate = dateEntry.getKey(); // 日期
                                List<OdsAttendance> hourlyData = dateEntry.getValue();

                                // 4. 聚合计算（求和）
                                int totalAttendance = hourlyData.stream()
                                        .filter(att -> att.getTotalAttendance() != null)
                                        .mapToInt(OdsAttendance::getTotalAttendance)
                                        .sum();

                                int annualPass = hourlyData.stream()
                                        .filter(att -> att.getAnnualPass() != null)
                                        .mapToInt(OdsAttendance::getAnnualPass)
                                        .sum();

                                int vipAttendance = hourlyData.stream()
                                        .filter(att -> att.getVipAttendance() != null)
                                        .mapToInt(OdsAttendance::getVipAttendance)
                                        .sum();
                                // 6. 构建 DailyAttendance 对象
                                OdsAttendance daily = new OdsAttendance();
                                daily.setReportHour(reportDate.atStartOfDay());
                                daily.setPark(park);
                                daily.setTotalAttendance(totalAttendance);
                                daily.setAnnualPass(annualPass);
                                daily.setVipAttendance(vipAttendance);
                                return daily;
                            });
                })
                .collect(Collectors.toList());
    }
    /**
     * 同步至目标表保存更新
     *
     * @param mergedData 合并后的数据
     * @param traceId    数据追踪id
     */
    protected int syncToTarget(Map<String, List<KpiRevenueAttendance>> mergedData, String traceId) {

        List<KpiRevenueAttendance> dbResults = new ArrayList<>();
        mergedData.forEach((key, datas) -> {
            for(KpiRevenueAttendance data : datas) {
                KpiRevenueAttendance target = new KpiRevenueAttendance();
                // 属性映射
                target.setRevenueGenerated(data.getRevenueGenerated());
                target.setParkCode(data.getParkCode());
                target.setAttendance(data.getAttendance());
                target.setVipVisitors(data.getVipVisitors());
                target.setApVisitors(data.getApVisitors());
                target.setDate(data.getDate());
                // 数据未更新暂时放0
                target.setOnsiteConciergeRevenue(BigDecimal.ZERO);
                target.setSynchTime(LocalDateTime.now());
                target.setSyncTraceId(traceId);
                dbResults.add(target);
            }
        });
        if (CollectionUtils.isNotEmpty(dbResults)) {
            // 批量保存或更新
            mapper.batchInsertOrUpdate(dbResults);
        }
        return dbResults.size();
    }

}
