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

import com.alibaba.fastjson2.JSON;
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.TableNameUtils;
import com.centreproject.common.websocket.MyWebSocketHandler;
import com.centreproject.system.domain.kpi.guestExperience.KpiYasIslandCampaigns;
import com.centreproject.system.domain.sync.ods.DailyCampaignStats;
import com.centreproject.system.domain.sync.ods.OdsEmarsys;
import com.centreproject.system.mapper.kpi.KpiYasIslandCampaignsMapper;
import com.centreproject.system.mapper.sync.ods.OdsEmarsysMapper;
import com.centreproject.system.service.guestExperience.IKpiYasIslandCampaignsService;
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.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.math.RoundingMode;
import java.sql.Date;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Guest Experience KPI Service Implementation
 *
 * @author zhonglei.xu
 */
@Service
@RequiredArgsConstructor
public class KpiYasIslandCampaignsServiceImpl
        extends ServiceImpl<KpiYasIslandCampaignsMapper, KpiYasIslandCampaigns>
        implements IKpiYasIslandCampaignsService {
    private Logger logger = LoggerFactory.getLogger(KpiYasIslandCampaignsServiceImpl.class);

    // 规则工厂
    private final SyncRuleHandlerFactory ruleHandlerFactory;
    // 同步日志
    private final IDataSyncLogService dataSyncService;
    // 事物模板
    private final TransactionTemplate transactionTemplate;
    private final Executor virtualThreadExecutor;
    private final OdsEmarsysMapper odsEmarsysMapper;
    @Autowired
    private KpiYasIslandCampaignsMapper mapper;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    // WebSocket消息发送器
    @Autowired
    private MyWebSocketHandler myWebSocketHandler;

    /**
     * Yas_Island_Campaigns last 60 days KPI
     *
     * @return
     */
    @Override
    public KpiYasIslandCampaigns getYasIslandCampaigns() {
        String redisKey = KeyConstants.YAS_ISLAND_CAMPAIGNS + DateUtils.getRiDate() + GuestConstants.LATEST_60_DAYS;

        // 1. 查缓存
        KpiYasIslandCampaigns cached = (KpiYasIslandCampaigns) redisTemplate.opsForValue().get(redisKey);
        if (cached != null) {
            return cached;
        }

        // 2. 根据类型处理数据
        KpiYasIslandCampaigns result = mapper.getYasIslandCampaignsLatest60Days();


        // 3. 写缓存（仅60天数据需要缓存）
        if (null!=result) {
            ruleProcess(result);
            ruleHandlerFactory.refreshRuleCache(KeyConstants.YAS_ISLAND_CAMPAIGNS);
            redisTemplate.opsForValue().set(redisKey, result, 1, TimeUnit.HOURS);
        }
        return result;
    }

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

            } finally {
                MDC.clear();
            }
        });
    }

    /**
     * 同步数据
     *
     * @param traceId
     * @param jobId
     */
    private void syncDataAll(String traceId, long jobId) {
        try {
            // 日志开始
            logger.info("【Yas Island Campaigns】开始同步数据");
            String sourceTable = TableNameUtils.getTableNames(KeyConstants.SPLIT, OdsEmarsys.class);
            String targetTable = TableNameUtils.getTableNames("", KpiYasIslandCampaigns.class);
            dataSyncService.startSync(jobId, traceId, DataSourceSystem.QUESTION_PRO.getCode(), sourceTable, targetTable);

            // 1. 查询源数据
            // 1.1 查询活动(邮件的处理时间非同一天处理，每次同步60天的数据)
            LocalDate endDate = LocalDate.now();
            LocalDate startDate = endDate.minusDays(60);
            logger.info("【Yas Island Campaigns】查询源数据，日期范围: {} 至 {}", startDate, endDate);
            List<DailyCampaignStats> dailyStats = odsEmarsysMapper.selectDailyCampaignStats(startDate, endDate);
            List<DailyCampaignStats> dailyCounts = odsEmarsysMapper.selectDailyCampaignCounts(startDate, endDate);
            // todo delivery ，aptotal，flenewed和expired待处理
            // 2. 合并数据并转换
            List<KpiYasIslandCampaigns> targetData = mergeAndTransformData(dailyStats, dailyCounts, traceId);
            if (CollectionUtils.isNotEmpty(targetData)) {
                // 3. 执行事务操作
                Integer affectedRows = transactionTemplate.execute(status -> {
                    try {
                        int count = mapper.batchInsertOrUpdate(targetData);
                        status.flush();
                        return count;
                    } catch (Exception e) {
                        // 设置回滚标记
                        status.setRollbackOnly();
                        throw e;
                    }
                });
                // 日志结束
                dataSyncService.endSync(traceId, affectedRows, SyncStatus.SUCCESS, "Yas_Island_Campaigns data synchronization completed successfully");
            } else {
                dataSyncService.endSync(traceId, 0, SyncStatus.FAILED, "无同步数据");
            }

            // websocket消息推送
            this.fetchAndPushData();
        } catch (DataAccessException e) {
            String errorMsg = String.format("数据库操作失败[表:%s]，错误: %s",
                    TableNameUtils.getTableNames(KeyConstants.SPLIT, KpiYasIslandCampaigns.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.YAS_ISLAND_CAMPAIGNS);
        }
    }

    /**
     * 聚合数据到目标表中
     *
     * @param dailyStats
     * @param dailyCounts
     * @param traceId
     * @return
     */
    private List<KpiYasIslandCampaigns> mergeAndTransformData(List<DailyCampaignStats> dailyStats, List<DailyCampaignStats> dailyCounts, String traceId) {
        // 将dailyCounts转换为按日期查找的Map
        Map<LocalDate, Integer> countMap = dailyCounts.stream()
                .collect(Collectors.toMap(
                        DailyCampaignStats::getReportDate,
                        DailyCampaignStats::getCampaignCount
                ));

        return dailyStats.stream()
                .map(stat -> {
                    KpiYasIslandCampaigns campaign = new KpiYasIslandCampaigns();
                    campaign.setDate(Date.valueOf(stat.getReportDate()));
                    // 设置统计指标
                    campaign.setMtdCampaigns(countMap.get(stat.getReportDate()));
                    campaign.setTotalCampaignEmailSent(stat.getTotalSent());
                    // todo 待处理
//                    campaign.setDelivered(stat.getTotalSent());

                    // 计算比率（避免除以零）
                    // 1. 计算邮件打开率（totalOpens / totalSent）
                    if (stat.getTotalSent() != null && stat.getTotalSent() > 0) {
                        BigDecimal totalOpens = BigDecimal.valueOf(stat.getTotalOpens() == null ? 0 : stat.getTotalOpens());
                        BigDecimal totalSent = BigDecimal.valueOf(stat.getTotalSent());
                        BigDecimal emailOpenRate = totalOpens.divide(totalSent, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                        campaign.setEmailOpenRate(emailOpenRate);
                    }

                    // 2. 计算点击率（totalClicks / totalSent）
                    if (stat.getTotalSent() != null && stat.getTotalSent() > 0) {
                        BigDecimal totalClicks = BigDecimal.valueOf(stat.getTotalClicks() == null ? 0 : stat.getTotalClicks());
                        BigDecimal totalSent = BigDecimal.valueOf(stat.getTotalSent());
                        BigDecimal clickRate = totalClicks.divide(totalSent, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                        campaign.setClickRate(clickRate);
                    }

                    // 设置同步信息
                    campaign.setSynchTime(LocalDateTime.now());
                    campaign.setSyncTraceId(traceId);

                    // todo 以下字段在源数据中不存在，设置为null
                    campaign.setAnnualPassholdersTotal(null);
                    campaign.setRenewed(null);
                    campaign.setExpired(null);

                    return campaign;
                })
                .collect(Collectors.toList());
    }


    /**
     * 模拟定时任务往websocket发送消息
     * 待数仓接通后可取消spring的调度job
     * Yas_Island_Campaigns daily kpi
     */

    public void fetchAndPushData() {
        try {
            // 1. 从数仓获取最新数据（示例）
            KpiYasIslandCampaigns latestData = mapper.getYasIslandCampaignsDailyData();
            // 2.规则处理
            this.ruleProcess(latestData);
            if (null != latestData) {
                // 2. 转换为JSON
                Map<String, Object> message = new HashMap<>();
                // 模块唯一标识
                message.put("module", KeyConstants.YAS_ISLAND_CAMPAIGNS);
                message.put("data", latestData);
                message.put("timestamp", System.currentTimeMillis());
                // 3. 通过WebSocket主动推送
                String jsonData = JSON.toJSONString(message);
                myWebSocketHandler.broadcast(jsonData);
                logger.info("数据推送成功，内容: {}", jsonData);
            }
        } catch (Exception e) {
            logger.error("数据获取或推送异常", e);
        }
    }

    /**
     * 规则处理
     *
     * @param kpi kpi
     */
    public void ruleProcess(KpiYasIslandCampaigns kpi) {
        if(null == kpi){
            return;
        }
        // 规则处理
        Integer mtd = ruleHandlerFactory.applyDefaultValue(KeyConstants.YAS_ISLAND_CAMPAIGNS, KpiIndexConstants.MTD, kpi.getMtdCampaigns(), Integer.class);
        ruleHandlerFactory.checkThreshold(KeyConstants.YAS_ISLAND_CAMPAIGNS, KpiIndexConstants.MTD, mtd);
        kpi.setMtdCampaigns(mtd);

        Integer sent = ruleHandlerFactory.applyDefaultValue(KeyConstants.YAS_ISLAND_CAMPAIGNS, KpiIndexConstants.TOTAL_CAMPAIGN_EMAIL_SENT, kpi.getTotalCampaignEmailSent(), Integer.class);
        ruleHandlerFactory.checkThreshold(KeyConstants.YAS_ISLAND_CAMPAIGNS, KpiIndexConstants.TOTAL_CAMPAIGN_EMAIL_SENT, sent);
        kpi.setTotalCampaignEmailSent(sent);

        BigDecimal openRate = ruleHandlerFactory.applyDefaultValue(KeyConstants.YAS_ISLAND_CAMPAIGNS, KpiIndexConstants.EMAIL_OPEN_RATE, kpi.getEmailOpenRate(), BigDecimal.class);
        ruleHandlerFactory.checkThreshold(KeyConstants.YAS_ISLAND_CAMPAIGNS, KpiIndexConstants.EMAIL_OPEN_RATE, openRate);
        kpi.setEmailOpenRate(openRate);

        BigDecimal clickRate = ruleHandlerFactory.applyDefaultValue(KeyConstants.YAS_ISLAND_CAMPAIGNS, KpiIndexConstants.CLICK_RATE, kpi.getClickRate(), BigDecimal.class);
        ruleHandlerFactory.checkThreshold(KeyConstants.YAS_ISLAND_CAMPAIGNS, KpiIndexConstants.CLICK_RATE, clickRate);
        kpi.setClickRate(clickRate);
    }


}