package com.moka.gdtauto.service;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.moka.gdtauto.client.TencentAdsApiClientFactory;
import com.moka.gdtauto.common.Constant;
import com.moka.gdtauto.entity.AccountGroupAccountRelationship;
import com.moka.gdtauto.entity.AdPlanConf;
import com.moka.gdtauto.entity.AdPlanCreative;
import com.moka.gdtauto.entity.BaseDailyCreativeReport;
import com.moka.gdtauto.entity.RepDailyCreativeReport;
import com.moka.gdtauto.entity.ReqDailyCreativeReport;
import com.moka.gdtauto.mapper.AccountGroupAccountRelationshipMapper;
import com.moka.gdtauto.mapper.AccountGroupMapper;
import com.moka.gdtauto.mapper.RepDailyCreativeReportMapper;
import com.moka.gdtauto.mapper.ReqDailyCreativeReportMapper;
import com.tencent.ads.model.v3.DailyReportsFilteringStruct;
import com.tencent.ads.model.v3.DailyReportsGetResponseData;
import com.tencent.ads.model.v3.FilterOperator;
import com.tencent.ads.model.v3.ReportDateRange;
import com.tencent.ads.model.v3.ReportStruct;
import com.tencent.ads.model.v3.TimeLine;
import com.tencent.ads.v3.TencentAds;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 动态创意日报表 Service
 * 支持双口径（REQUEST_TIME / REPORTING_TIME）同步与查询
 *
 * @author moka
 * @since 2026-03-03
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DailyCreativeReportService {

    private final TencentAdsApiClientFactory clientFactory;
    private final TencentAccountService accountService;
    private final AccountGroupAccountRelationshipMapper relationshipMapper;
    private final AccountGroupMapper accountGroupMapper;
    private final ReqDailyCreativeReportMapper reqMapper;
    private final RepDailyCreativeReportMapper repMapper;
    private final AdPlanConfService adPlanConfService;
    private final AdPlanCreativeService adPlanCreativeService;

    /** 同步时请求的指标字段 */
    private static final List<String> SYNC_FIELDS = Arrays.asList(
        "account_id",
            "dynamic_creative_id", "dynamic_creative_name", "date",
            // 曝光点击
            "view_count", "view_user_count", "avg_view_per_user",
            "valid_click_count", "click_user_count", "cpc", "ctr",
            "valuable_click_count", "valuable_click_cost", "valuable_click_rate",
            // 花费
            "cost", "thousand_display_price",
            // 转化
            "conversions_count", "conversions_rate", "conversions_cost",
            "deep_conversions_count", "deep_conversions_rate", "deep_conversions_cost",
            "conversions_by_display_count", "conversions_by_click_count",
            "preview_conversions_count", "preview_deep_conversions_count",
            // 视频
            "video_outer_play_count", "video_outer_play_user_count", "avg_user_play_count",
            "video_outer_play_time_count", "video_outer_play_rate", "video_outer_play_cost",
            "video_outer_play3s_count", "video_outer_play3s_rate",
            "video_outer_play25_count", "video_outer_play50_count",
            "video_outer_play75_count", "video_outer_play100_count",
            // 互动
            "read_count", "comment_count", "praise_count", "forward_count",
            // 落地页
            "platform_page_view_count", "platform_page_view_rate",
            "lan_button_click_count", "lan_jump_button_clickers",
            // 关键页面
            "key_page_view_count", "key_page_uv",
            // ROI/LTV
            "income_val_1", "income_roi_1",
            "income_val_3", "income_roi_3",
            "income_val_7", "income_roi_7",
            "income_val_14", "income_roi_14",
            "income_val_24h", "income_roi_1_24h"
    );

    // ─── 同步方法 ─────────────────────────────────────────────────────────────

    /**
     * 同步指定账户的日报表（分页拉取直到全量完成）
     *
     * @param jobNumber    工号（用于获取 access_token）
     * @param orgAccountId 组织账户ID
     * @param accountId    广告账户ID
     * @param groupId      账号组ID
     * @param groupName    账号组名称
     * @param startDate    开始日期 yyyy-MM-dd
     * @param endDate      结束日期 yyyy-MM-dd
     * @param timeLine     时间口径 REQUEST_TIME / REPORTING_TIME
     * @return 成功写入行数
     */
    public void syncReport(AdPlanConf conf,
                          String startDate, String endDate, String timeLine) throws Exception {
        log.info("开始同步日报表 timeLine={} [{}, {}]", timeLine, startDate, endDate);

        TencentAds tencentAds = clientFactory.getTencentAds(null, Constant.MAIN_ORG_ACCOUNT_ID);
        tencentAds.setDebug(true);
        ReportDateRange dateRange = new ReportDateRange();
        dateRange.setStartDate(startDate);
        dateRange.setEndDate(endDate);
        List<String> groupBy = Arrays.asList("dynamic_creative_id", "date");

        Long accountGroupId = conf.getAccountGroupId();
        List<AccountGroupAccountRelationship> relationships = relationshipMapper.selectList(
            new LambdaQueryWrapper<AccountGroupAccountRelationship>()
                .eq(AccountGroupAccountRelationship::getGroupId, accountGroupId)
        );

        for (AccountGroupAccountRelationship relationship : relationships) {
            Long accountId = relationship.getAccountId();
            
            // 分页查询 AdPlanCreative，条件：status=4, systemDelete='false'
        int pageNum = 1;
        int pageSize = 100;
        while (true) {
            Page<AdPlanCreative> page = adPlanCreativeService.page(
                new Page<>(pageNum, pageSize),
                new LambdaQueryWrapper<AdPlanCreative>()
                    .eq(AdPlanCreative::getAccountId, accountId)
                    .eq(AdPlanCreative::getAdPlanConfId, conf.getId())
                    .eq(AdPlanCreative::getStatus, 4)
                    .eq(AdPlanCreative::getSystemDelete, "false")
            );

            List<AdPlanCreative> creativeList = page.getRecords();
            if (creativeList.isEmpty()) {
                break;
            }

            // 提取 creativeId 列表
            List<Long> creativeIds = creativeList.stream()
                .map(AdPlanCreative::getCreativeId)
                .toList();

            List<DailyReportsFilteringStruct> filtering = new ArrayList<>();
            DailyReportsFilteringStruct struct = new DailyReportsFilteringStruct();
            struct.setField("dynamic_creative_id");
            struct.setOperator(FilterOperator.IN);
            struct.setValues(creativeIds.stream().map(String::valueOf).toList());
            filtering.add(struct);

            DailyReportsGetResponseData response = tencentAds.dailyReports().dailyReportsGet(
                    "REPORT_LEVEL_DYNAMIC_CREATIVE",
                    dateRange,
                    groupBy,
                    SYNC_FIELDS,
                    accountId,
                    filtering,
                    null,
                    timeLine,
                    1L,
                    100L,
                    null
            );

            if (response == null || response.getList() == null || response.getList().isEmpty()) {
                pageNum++;
                continue;
            }

            List<ReportStruct> list = response.getList();
            if (TimeLine.REQUEST_TIME.getValue().equals(timeLine)) {
                List<ReqDailyCreativeReport> entities = new ArrayList<>();
                for (ReportStruct rs : list) {
                    if (rs.getDynamicCreativeId() == null || rs.getDate() == null) {
                        continue;
                    }
                    entities.add(toReqEntity(conf.getId(), conf.getAccountGroupId(), rs));
                }
                if (!entities.isEmpty()) {
                    reqMapper.batchUpsert(entities);
                }
            } else {
                List<RepDailyCreativeReport> entities = new ArrayList<>();
                for (ReportStruct rs : list) {
                    if (rs.getDynamicCreativeId() == null || rs.getDate() == null) {
                        continue;
                    }
                    entities.add(toRepEntity(conf.getId(), conf.getAccountGroupId(), rs));
                }
                if (!entities.isEmpty()) {
                    repMapper.batchUpsert(entities);
                }
            }

            // 判断是否还有下一页
            if (!page.hasNext()) {
                break;
            }
            pageNum++;
        }
     }

        
    }

    /**
     * 同步所有账户（定时任务调用）——通过 AccountGroupAccountRelationship 关联获取 groupId / groupName
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @param timeLine  时间口径
     * @return 成功保存总行数
     */
    public void syncAllAccounts(Long confId, String startDate, String endDate, String timeLine) {
        // 获取所有广告计划配置
        List<AdPlanConf> confList = adPlanConfService.list(
            new LambdaQueryWrapper<AdPlanConf>()
                .eq(confId != null, AdPlanConf::getId, confId)
                .eq(AdPlanConf::getAdType, 2)
                .in(AdPlanConf::getStatus, List.of(4,5))
                .eq(AdPlanConf::getDeleted, 0));
        log.info("查询到 {} 个广告计划配置", confList.size());

        if (confList.isEmpty()) {
            return;
        }

        // for (AdPlanConf conf : confList) {
        //         try {
        //                 syncReport(conf, startDate, endDate, timeLine);
        //             } catch (Exception e) {
        //                 log.error("同步创意日报 {} 失败", conf.getId(), e);
        //             }
        //     }

        // 使用 Semaphore 控制并发数为 10
        Semaphore semaphore = new Semaphore(10);
        // 使用虚拟线程池进行并发处理
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (AdPlanConf conf : confList) {
                executor.submit(() -> {
                    try {
                        semaphore.acquire();
                        syncReport(conf, startDate, endDate, timeLine);
                    } catch (InterruptedException e) {
                        log.error("获取信号量被中断", e);
                        Thread.currentThread().interrupt();
                    } catch (Exception e) {
                        log.error("同步创意日报 {} 失败", conf.getId(), e);
                    }  finally {
                        semaphore.release();
                    }
                });
            }
            // 关闭线程池并等待所有任务完成
            executor.shutdown();
            boolean terminated = executor.awaitTermination(30, TimeUnit.MINUTES);
            if (!terminated) {
                log.warn("同步创意日报 任务未在指定时间内完成");
            }
        } catch (InterruptedException e) {
            log.error("并发处理被中断", e);
            Thread.currentThread().interrupt();
        }

        log.info("所有广告计划定向更新完成");

    }

    // ─── 查询方法 ─────────────────────────────────────────────────────────────

    /**
     * 分页查询日报表
     *
     * @param timeLine  时间口径（决定查哪张表）
     * @param groupId   可选，账号组ID
     * @param accountId 可选，广告账户ID
     * @param startDate 可选，开始日期
     * @param endDate   可选，结束日期
     * @param pageNum   页码
     * @param pageSize  每页大小
     * @return 分页结果
     */
    public Page<? extends BaseDailyCreativeReport> listReports(
            String timeLine, Long groupId, Long accountId, Long dynamicCreativeId,
            String startDate, String endDate, long pageNum, long pageSize) {

        if ("REPORTING_TIME".equals(timeLine)) {
            LambdaQueryWrapper<RepDailyCreativeReport> wrapper = buildRepWrapper(groupId, accountId, dynamicCreativeId, startDate, endDate);
            return repMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
        } else {
            LambdaQueryWrapper<ReqDailyCreativeReport> wrapper = buildReqWrapper(groupId, accountId, dynamicCreativeId, startDate, endDate);
            return reqMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
        }
    }

    private LambdaQueryWrapper<ReqDailyCreativeReport> buildReqWrapper(
            Long groupId, Long accountId, Long dynamicCreativeId, String startDate, String endDate) {
        LambdaQueryWrapper<ReqDailyCreativeReport> wrapper = new LambdaQueryWrapper<>();
        if (groupId != null) {
            wrapper.eq(ReqDailyCreativeReport::getGroupId, groupId);
        }
        if (accountId != null) {
            wrapper.eq(ReqDailyCreativeReport::getAccountId, accountId);
        }
        if (dynamicCreativeId != null) {
            wrapper.eq(ReqDailyCreativeReport::getDynamicCreativeId, dynamicCreativeId);
        }
        if (StringUtils.hasText(startDate)) {
            wrapper.ge(ReqDailyCreativeReport::getDate, LocalDate.parse(startDate));
        }
        if (StringUtils.hasText(endDate)) {
            wrapper.le(ReqDailyCreativeReport::getDate, LocalDate.parse(endDate));
        }
        wrapper.orderByDesc(ReqDailyCreativeReport::getDate);
        return wrapper;
    }

    private LambdaQueryWrapper<RepDailyCreativeReport> buildRepWrapper(
            Long groupId, Long accountId, Long dynamicCreativeId, String startDate, String endDate) {
        LambdaQueryWrapper<RepDailyCreativeReport> wrapper = new LambdaQueryWrapper<>();
        if (groupId != null) {
            wrapper.eq(RepDailyCreativeReport::getGroupId, groupId);
        }
        if (accountId != null) {
            wrapper.eq(RepDailyCreativeReport::getAccountId, accountId);
        }
        if (dynamicCreativeId != null) {
            wrapper.eq(RepDailyCreativeReport::getDynamicCreativeId, dynamicCreativeId);
        }
        if (StringUtils.hasText(startDate)) {
            wrapper.ge(RepDailyCreativeReport::getDate, LocalDate.parse(startDate));
        }
        if (StringUtils.hasText(endDate)) {
            wrapper.le(RepDailyCreativeReport::getDate, LocalDate.parse(endDate));
        }
        wrapper.orderByDesc(RepDailyCreativeReport::getDate);
        return wrapper;
    }

    // ─── 映射方法 ─────────────────────────────────────────────────────────────

    private ReqDailyCreativeReport toReqEntity(Long confId, Long groupId,  ReportStruct rs) {
        ReqDailyCreativeReport e = new ReqDailyCreativeReport();
        fillFields(e,confId, groupId, rs);
        return e;
    }

    private RepDailyCreativeReport toRepEntity(Long confId, Long groupId, ReportStruct rs) {
        RepDailyCreativeReport e = new RepDailyCreativeReport();
        fillFields(e,confId, groupId, rs);
        return e;
    }

    private void fillFields(BaseDailyCreativeReport e, Long confId, Long groupId, ReportStruct rs) {
        e.setConfId(confId);
        e.setAccountId(rs.getAccountId());
        e.setGroupId(groupId);
        e.setDynamicCreativeId(rs.getDynamicCreativeId());
        e.setDynamicCreativeName(rs.getDynamicCreativeName());
        e.setDate(LocalDate.parse(rs.getDate()));
        // 基础
        e.setViewCount(rs.getViewCount());
        e.setViewUserCount(rs.getViewUserCount());
        e.setAvgViewPerUser(d2b(rs.getAvgViewPerUser()));
        e.setValidClickCount(rs.getValidClickCount());
        e.setClickUserCount(rs.getClickUserCount());
        e.setCpc(rs.getCpc());
        e.setCtr(d2b(rs.getCtr()));
        e.setValuableClickCount(rs.getValuableClickCount());
        e.setValuableClickCost(rs.getValuableClickCost());
        e.setValuableClickRate(d2b(rs.getValuableClickRate()));
        // 花费
        e.setCost(rs.getCost());
        e.setAcquisitionCost(rs.getAcquisitionCost());
        e.setThousandDisplayPrice(rs.getThousandDisplayPrice());
        // 转化
        e.setConversionsCount(rs.getConversionsCount());
        e.setConversionsRate(d2b(rs.getConversionsRate()));
        e.setConversionsCost(rs.getConversionsCost());
        e.setDeepConversionsCount(rs.getDeepConversionsCount());
        e.setDeepConversionsRate(d2b(rs.getDeepConversionsRate()));
        e.setDeepConversionsCost(rs.getDeepConversionsCost());
        e.setConversionsByDisplayCount(rs.getConversionsByDisplayCount());
        e.setConversionsByDisplayRate(d2b(rs.getConversionsByDisplayRate()));
        e.setConversionsByDisplayCost(rs.getConversionsByDisplayCost());
        e.setConversionsByClickCount(rs.getConversionsByClickCount());
        e.setConversionsByClickRate(d2b(rs.getConversionsByClickRate()));
        e.setConversionsByClickCost(rs.getConversionsByClickCost());
        e.setPreviewConversionsCount(rs.getPreviewConversionsCount());
        e.setPreviewDeepConversionsCount(rs.getPreviewDeepConversionsCount());
        // 视频
        e.setVideoOuterPlayCount(rs.getVideoOuterPlayCount());
        e.setVideoOuterPlayUserCount(rs.getVideoOuterPlayUserCount());
        e.setAvgUserPlayCount(d2b(rs.getAvgUserPlayCount()));
        e.setVideoOuterPlayTimeCount(d2b(rs.getVideoOuterPlayTimeCount()));
        e.setVideoOuterPlayTimeAvgRate(d2b(rs.getVideoOuterPlayTimeAvgRate()));
        e.setVideoOuterPlayRate(d2b(rs.getVideoOuterPlayRate()));
        e.setVideoOuterPlayCost(rs.getVideoOuterPlayCost());
        e.setVideoOuterPlay10Count(rs.getVideoOuterPlay10Count());
        e.setVideoOuterPlay25Count(rs.getVideoOuterPlay25Count());
        e.setVideoOuterPlay50Count(rs.getVideoOuterPlay50Count());
        e.setVideoOuterPlay75Count(rs.getVideoOuterPlay75Count());
        e.setVideoOuterPlay90Count(rs.getVideoOuterPlay90Count());
        e.setVideoOuterPlay95Count(rs.getVideoOuterPlay95Count());
        e.setVideoOuterPlay100Count(rs.getVideoOuterPlay100Count());
        e.setVideoOuterPlay3sCount(rs.getVideoOuterPlay3sCount());
        e.setVideoOuterPlay3sRate(d2b(rs.getVideoOuterPlay3sRate()));
        e.setVideoOuterPlay5sCount(rs.getVideoOuterPlay5sCount());
        e.setVideoOuterPlay7sCount(rs.getVideoOuterPlay7sCount());
        // 互动
        e.setReadCount(rs.getReadCount());
        e.setReadCost(rs.getReadCost());
        e.setCommentCount(rs.getCommentCount());
        e.setCommentCost(rs.getCommentCost());
        e.setPraiseCount(rs.getPraiseCount());
        e.setPraiseCost(rs.getPraiseCost());
        e.setForwardCount(rs.getForwardCount());
        e.setForwardCost(rs.getForwardCost());
        e.setNoInterestCount(rs.getNoInterestCount());
        // 落地页
        e.setPlatformPageViewCount(rs.getPlatformPageViewCount());
        e.setPlatformPageViewRate(d2b(rs.getPlatformPageViewRate()));
        e.setLanButtonClickCount(rs.getLanButtonClickCount());
        e.setLanJumpButtonClickers(rs.getLanJumpButtonClickers());
        e.setLanButtonClickCost(rs.getLanButtonClickCost());
        e.setLanJumpButtonCtr(d2b(rs.getLanJumpButtonCtr()));
        e.setLanJumpButtonClickCost(rs.getLanJumpButtonClickCost());
        // 关键页面
        e.setKeyPageViewCount(rs.getKeyPageViewCount());
        e.setKeyPageUv(rs.getKeyPageUv());
        // ROI/LTV - 注意：SDK字段名 incomeRoi124h 对应 API income_roi_1_24h
        e.setIncomeVal1(rs.getIncomeVal1());
        e.setIncomeRoi1(d2b(rs.getIncomeRoi1()));
        e.setIncomeVal3(rs.getIncomeVal3());
        e.setIncomeRoi3(d2b(rs.getIncomeRoi3()));
        e.setIncomeVal7(rs.getIncomeVal7());
        e.setIncomeRoi7(d2b(rs.getIncomeRoi7()));
        e.setIncomeVal14(rs.getIncomeVal14());
        e.setIncomeRoi14(d2b(rs.getIncomeRoi14()));
        e.setIncomeVal24h(rs.getIncomeVal24h());
        e.setIncomeRoi124h(d2b(rs.getIncomeRoi124h()));
    }

    /** Double → BigDecimal 安全转换 */
    private BigDecimal d2b(Double d) {
        return d == null ? null : BigDecimal.valueOf(d);
    }
}
