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

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.StringUtils;
import com.centreproject.common.utils.TableNameUtils;
import com.centreproject.common.websocket.MyWebSocketHandler;
import com.centreproject.system.domain.kpi.Park;
import com.centreproject.system.domain.sync.kpi.KpiDailyParkRevenue;
import com.centreproject.system.domain.sync.ods.GxCcDailyParkIgRevenueOds;
import com.centreproject.system.domain.sync.ods.GxCcDailyParkOmniRevenueOds;
import com.centreproject.system.domain.sync.ods.OdsRevenue;
import com.centreproject.system.domain.vo.RevenueVo;
import com.centreproject.system.mapper.kpi.ParkMapper;
import com.centreproject.system.mapper.sync.kpi.KpiDailyParkRevenueMapper;
import com.centreproject.system.mapper.sync.ods.OdsRevenueMapper;
import com.centreproject.system.service.job.factory.SyncRuleHandlerFactory;
import com.centreproject.system.service.sync.IDataSyncLogService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 园区收入分析同步服务
 *
 * @author zhonglei.xu
 */
@Service
@RequiredArgsConstructor
public class KpiDailyParkRevenueSyncService {
    private final Logger logger = LoggerFactory.getLogger(KpiDailyParkRevenueSyncService.class);
    private static final ZoneId RIYADH_ZONE = ZoneId.of("Asia/Riyadh");
    private final OdsRevenueMapper odsRevenueMapper;
    private final KpiDailyParkRevenueMapper kpiRevenueMapper;
    private final MyWebSocketHandler webSocketHandler;
    private final Executor virtualThreadExecutor;
    private final ParkMapper parkMapper;
    private final IDataSyncLogService dataSyncService;
    private final TransactionTemplate transactionTemplate;
    // 规则工厂
    private final SyncRuleHandlerFactory ruleHandlerFactory;

    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("【Park Revenue】开始同步数据");
            String sourceTable = TableNameUtils.getTableNames("", OdsRevenue.class);
            String targetTable = TableNameUtils.getTableNames("", KpiDailyParkRevenue.class);
            dataSyncService.startSync(jobId, traceId, DataSourceSystem.OMNI.getCode(), sourceTable, targetTable);

            // 沙特当前时间
            LocalDate today = LocalDate.now(RIYADH_ZONE);

            // 1. 查询源数据
            List<KpiDailyParkRevenue> kpiDailyParkRevenues = kpiRevenueMapper.selectList(null);

            List<OdsRevenue> odsRevenues;

            if (CollectionUtils.isNotEmpty(kpiDailyParkRevenues)) {
                odsRevenues = odsRevenueMapper
                        .selectList(new LambdaQueryWrapper<OdsRevenue>()
                                .eq(OdsRevenue::getReportDate, today));
            } else {
                odsRevenues = odsRevenueMapper.selectList(null);
            }

            // 全量园区数据
            List<Park> parks = parkMapper.selectList(null);

            // 2. 数据聚合
            Map<String, List<KpiDailyParkRevenue>> mergedData = mergeDataAll(odsRevenues, parks);
            // 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, "Park revenue data synchronization completed successfully");

            // 4. 发送WebSocket通知
            createWebSocketData(mergedData);
        } catch (DataAccessException e) {
            String errorMsg = String.format("数据库操作失败[表:%s]，错误: %s",
                    TableNameUtils.getTableNames(KeyConstants.SPLIT, KpiDailyParkRevenue.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);
        }

    }

    /**
     * 规则处理
     *
     * @param lists 数据
     */
    public void getRuleProcess(List<KpiDailyParkRevenue> lists) {
        if (CollectionUtils.isNotEmpty(lists)) {
            for (KpiDailyParkRevenue revenue : lists) {
                BigDecimal totalRetailRevenue = ruleHandlerFactory.applyDefaultValue(KeyConstants.REVENUE,
                        KpiIndexConstants.RETAIL_REVENUE,
                        revenue.getRetailRevenue(),
                        BigDecimal.class);
                ruleHandlerFactory.checkThreshold(KeyConstants.REVENUE, KpiIndexConstants.RETAIL_REVENUE, totalRetailRevenue);
                revenue.setRetailRevenue(totalRetailRevenue);
                BigDecimal totalTicketRevenue = ruleHandlerFactory.applyDefaultValue(KeyConstants.REVENUE,
                        KpiIndexConstants.TICKET_REVENUE,
                        revenue.getTicketRevenue(),
                        BigDecimal.class);
                ruleHandlerFactory.checkThreshold(KeyConstants.REVENUE, KpiIndexConstants.TICKET_REVENUE, totalTicketRevenue);
                revenue.setTicketRevenue(totalTicketRevenue);
            }
        }

    }

    /**
     * 合并数据
     *
     * @param odsRevenues 收入
     * @param parks       园区列表
     * @return 合并后的数据
     */
    private Map<String, List<KpiDailyParkRevenue>> mergeDataAll(List<OdsRevenue> odsRevenues,
                                                                List<Park> parks) {
        Map<String, List<OdsRevenue>> revenueMap = odsRevenues.stream()
                .collect(Collectors.groupingBy(OdsRevenue::getPark));
        Map<String, String> parkMap = parks.stream().collect(Collectors.toMap(Park::getName, Park::getCode));
        Map<String, List<KpiDailyParkRevenue>> mergedData = new HashMap<>();
        // 合并数据
        for (Park park : parks) {
            String parkCode = parkMap.get(park.getName());
            if (StringUtils.isNotEmpty(parkCode)) {
                List<OdsRevenue> lists = revenueMap.get(park.getName());
                List<KpiDailyParkRevenue> results = new ArrayList<>();
                if (CollectionUtils.isNotEmpty(lists)) {
                    for (OdsRevenue odsRevenue : lists) {
                        KpiDailyParkRevenue kpiDailyParkRevenue = new KpiDailyParkRevenue();
                        kpiDailyParkRevenue.setReportDate(odsRevenue.getReportDate());
                        kpiDailyParkRevenue.setParkCode(parkCode);
                        // 门票收入
                        kpiDailyParkRevenue.setTicketRevenue(odsRevenue.getTotalTicketRevenue());
                        // 园区内交易收入
                        kpiDailyParkRevenue.setRetailRevenue(odsRevenue.getTotalRetailRevenue());
                        results.add(kpiDailyParkRevenue);
                    }
                }
                mergedData.put(parkCode, results);
            }
        }
        return mergedData;
    }

    /**
     * 同步至目标表保存更新
     *
     * @param mergedData 合并后的数据
     * @param traceId    数据追踪id
     */
    protected int syncToTarget(Map<String, List<KpiDailyParkRevenue>> mergedData, String traceId) {

        List<KpiDailyParkRevenue> dbResults = new ArrayList<>();
        mergedData.forEach((key, datas) -> {
            for (KpiDailyParkRevenue data : datas) {
                KpiDailyParkRevenue target = new KpiDailyParkRevenue();
                // 属性映射
                target.setReportDate(data.getReportDate());
                target.setParkCode(data.getParkCode());
                target.setTicketRevenue(data.getTicketRevenue());
                target.setRetailRevenue(data.getRetailRevenue());
                target.setSynchTime(Date.from(ZonedDateTime.now(RIYADH_ZONE).toInstant()));
                target.setSyncTraceId(traceId);
                dbResults.add(target);
            }
        });
        if (CollectionUtils.isNotEmpty(dbResults)) {
            // 批量保存或更新
            kpiRevenueMapper.batchInsertOrUpdate(dbResults);
        }
        return dbResults.size();
    }

    /**
     * 构建WebSocket通知数据
     *
     * @param mergedData 合并后的数据
     */
    private void createWebSocketData(Map<String, List<KpiDailyParkRevenue>> mergedData) {
        virtualThreadExecutor.execute(() -> {
            // 查询近30天的数据(目前查询的是目标表, 未来可能需要查询源表)
            List<KpiDailyParkRevenue> last30DaysData = kpiRevenueMapper.selectList(new LambdaQueryWrapper<KpiDailyParkRevenue>()
                    .ge(KpiDailyParkRevenue::getReportDate, Date.from(ZonedDateTime.now(RIYADH_ZONE).minusDays(30).toInstant()))
                    .orderByAsc(KpiDailyParkRevenue::getReportDate));
            // 历史数据规则预警检查
            getRuleProcess(last30DaysData);
            // 按日期+园区分组
            Map<String, KpiDailyParkRevenue> map = last30DaysData.stream().collect(Collectors.toMap(x -> x.getReportDate() + "," + x.getParkCode(), Function.identity()));
            // 将近30天的园区数据按照园区分组
            Map<String, List<KpiDailyParkRevenue>> groupByParkMap = last30DaysData.stream()
                    .collect(Collectors.groupingBy(KpiDailyParkRevenue::getParkCode));
            LocalDate today = LocalDate.now(RIYADH_ZONE);
            // 3. 按园区分组推送
            mergedData.forEach((parkCode, parkDatas) -> {
                // 规则检查
                getRuleProcess(parkDatas);
                for (KpiDailyParkRevenue parkData : parkDatas) {
                    if (parkData.getReportDate().equals(today)) {
                        RevenueVo revenueVo = new RevenueVo();
                        LocalDate previousDay = parkData.getReportDate().minusDays(1);
                        String key = previousDay + "," + parkCode;
                        // 前一天的数据 做环比用
                        KpiDailyParkRevenue previousData = map.get(key);
                        if (Objects.nonNull(previousData)) {
                            if (previousData.getRetailRevenue().compareTo(BigDecimal.ZERO) != 0 && parkData.getRetailRevenue().compareTo(BigDecimal.ZERO) != 0) {
                                BigDecimal subtract = parkData.getRetailRevenue().subtract(previousData.getRetailRevenue());
                                BigDecimal retailRate = subtract.divide(parkData.getTicketRevenue(), 4, RoundingMode.HALF_UP)
                                        .multiply(BigDecimal.valueOf(100));
                                revenueVo.setRetailRevenueRate(retailRate);
                            }
                            if (previousData.getTicketRevenue().compareTo(BigDecimal.ZERO) != 0 && parkData.getTicketRevenue().compareTo(BigDecimal.ZERO) != 0) {
                                BigDecimal subtract = parkData.getTicketRevenue().subtract(previousData.getTicketRevenue());
                                BigDecimal ticketRate = subtract.divide(parkData.getTicketRevenue(), 4, RoundingMode.HALF_UP)
                                        .multiply(BigDecimal.valueOf(100));
                                revenueVo.setTicketRevenueRate(ticketRate);
                            }
                        }
                        revenueVo.setRetailRevenue(parkData.getRetailRevenue());
                        revenueVo.setTicketRevenue(parkData.getTicketRevenue());
                        revenueVo.setLists(groupByParkMap.get(parkCode));
                        // 构建每个园区的独立消息
                        Map<String, Object> message = new HashMap<>();
                        message.put("module", KeyConstants.REVENUE + "," + parkCode);
                        message.put("data", revenueVo);
                        message.put("timestamp", System.currentTimeMillis());
                        // 转换为JSON并推送
                        String jsonData = JSON.toJSONString(message);
                        webSocketHandler.broadcast(jsonData);
                        logger.info("模块{}园区[{}]数据推送成功，数据: {}", KeyConstants.REVENUE, parkCode, jsonData);
                    }
                }
            });
        });
    }
}
