package com.moka.gdtauto.service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.google.common.collect.Lists;
import com.moka.gdtauto.client.TencentAdsApiClientFactory;
import com.moka.gdtauto.common.Constant;
import com.moka.gdtauto.entity.AdPlanCreative;
import com.moka.gdtauto.mapper.AdPlanConfMapper;
import com.moka.gdtauto.mapper.AdPlanCreativeMapper;
import com.tencent.ads.exception.TencentAdsResponseException;
import com.tencent.ads.model.v3.DailyReportsGetResponseData;
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 jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 广告计划日报表同步服务
 * 按广告组级别同步日报数据到 ad_plan 表
 *
 * @author moka
 * @since 2026-03-10
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AdPlanCreativeDailyReportService {

    private final TencentAdsApiClientFactory clientFactory;
    private final AdPlanConfMapper adPlanConfMapper;
    private final AdPlanCreativeMapper adPlanCreativeMapper;
    private final AdPlanCreativeService adplanCreativeService;

    /** REQUEST_TIME 口径同步字段 */
    private static final List<String> REQ_FIELDS = Arrays.asList(
            "adgroup_id", "dynamic_creative_id", "date", "cost", "income_val_1", "income_roi_1",
            "view_count", "valid_click_count", "ctr", "conversions_cost" ,"scan_follow_user_cost"
    );

    /** REPORTING_TIME 口径同步字段 */
    private static final List<String> REP_FIELDS = Arrays.asList(
            "adgroup_id",  "dynamic_creative_id", "date", "income_val_1", "income_roi_1"
    );

    @Async
    public void syncDailyReport(Long confId) {
        LocalDateTime start = LocalDateTime.now();
        LocalDateTime end = LocalDateTime.now();
        syncReqDailyReport(confId, start, end);
        syncRepDailyReport(confId, start, end);
    }

    /**
     * 同步 REQUEST_TIME 口径日报数据到 ad_plan_creative 表
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     */
    public void syncReqDailyReport(Long confId, LocalDateTime startTime, LocalDateTime endTime) {
        log.info("[syncCreativeReqDailyReport] 开始同步 REQUEST_TIME 口径日报 [{}, {}]", startTime, endTime);
        doSyncDailyReport(confId, startTime, endTime, TimeLine.REQUEST_TIME.getValue(), true);
    }

    /**
     * 同步 REPORTING_TIME 口径日报数据到 ad_plan_creative 表
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     */
    public void syncRepDailyReport(Long confId, LocalDateTime startTime, LocalDateTime endTime) {
        log.info("[syncCreativeRepDailyReport] 开始同步 REPORTING_TIME 口径日报 [{}, {}]", startTime, endTime);
        doSyncDailyReport(confId, startTime, endTime, TimeLine.REPORTING_TIME.getValue(), false);
    }

    /**
     * 执行同步逻辑
     *
     * @param startTime   开始时间
     * @param endTime     结束时间
     * @param timeLine    时间口径
     * @param isReqTime   是否为 REQUEST_TIME 口径（决定更新哪些字段）
     */
    private void doSyncDailyReport(@NotNull Long confId, LocalDateTime startTime, LocalDateTime endTime, String timeLine, boolean isReqTime) {
        List<AdPlanCreative> adPlanCreatives = adplanCreativeService.lambdaQuery()
        .select(AdPlanCreative::getAccountId, AdPlanCreative::getCreativeId)
        .gt(AdPlanCreative::getCreativeId, 0)
        .eq(AdPlanCreative::getAdPlanConfId, confId)
        .list();
        Set<Long> creativeIdSet = adPlanCreatives.stream().map(AdPlanCreative::getCreativeId).collect(Collectors.toSet());
        Set<Long> accountIdSet = adPlanCreatives.stream().map(AdPlanCreative::getAccountId).collect(Collectors.toSet());
        if (creativeIdSet.isEmpty()) {
            log.warn("[doSyncDailyReport] 没有创意数据，不进行同步");
            return;
        }
        String startStr = startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String endStr = endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        ReportDateRange dateRange = new ReportDateRange();
        dateRange.setStartDate(startStr);
        dateRange.setEndDate(endStr);

        List<String> groupBy = Arrays.asList("dynamic_creative_id", "date");
        List<String> fields = isReqTime ? REQ_FIELDS : REP_FIELDS;

        try {
            TencentAds tencentAds = clientFactory.getTencentAdsWithoutUserToken(Constant.MAIN_ORG_ACCOUNT_ID);
            for (Long accountId : accountIdSet) {
                syncBatch(tencentAds, accountId, creativeIdSet, dateRange, groupBy, fields, timeLine, isReqTime);
            }
        } catch (Exception e) {
            log.error("[doSyncDailyReport] 同步失败 confId={} startTime={} endTime={} timeLine={} isReqTime={}", confId, startTime, endTime, timeLine, isReqTime, e);
        }
    }

    /**
     * 处理单个 AdPlanConf
     */


    /**
     * 批量同步单批 adPlan
     * 支持分页循环获取所有数据，每页数据立即处理并更新
     */
    private int syncBatch(TencentAds tencentAds, Long accountId, Set<Long> creativeIdSet,
                          ReportDateRange dateRange, List<String> groupBy, List<String> fields,
                          String timeLine, boolean isReqTime) throws Exception {
        // 用于跟踪已处理的creativeId，避免重复处理
        Set<Long> processedCreativeIds = new java.util.HashSet<>();
        Long page = 1L;
        Long pageSize = 2000L;
        boolean hasMore = true;
        int totalUpdated = 0;

        // 分页循环获取所有数据，每页立即处理
        while (hasMore) {
            DailyReportsGetResponseData response = null;
            final Long currentPage = page;
            try {
                response = tencentAds.dailyReports().dailyReportsGet(
                        "REPORT_LEVEL_DYNAMIC_CREATIVE",
                        dateRange,
                        groupBy,
                        fields,
                        accountId,
                        null,
                        null,  // orderBy
                        timeLine,
                        currentPage,
                        pageSize,
                        null   // modularDetailMode
                );
            } catch (TencentAdsResponseException e) {
                // 11016 Application has reached the request limit per second.
                // 应用程序已达到每秒的请求限制，请稍后再试
                // 11017 Application has reached the request limit per minute.
                // 应用请求已达分钟频次上限，请登陆开发者后台查看当前接口对应分配的频次，并在要求范围内请求接口。
                if (e.getCode() == 11016) {
                    log.warn("请求达到每秒限制，请稍后再试");
                    TimeUnit.SECONDS.sleep(1);
                } else if (e.getCode() == 11017) {
                    log.warn("请求达到每分钟限制，请稍后再试");
                    // 计算出还剩多少时间
                    LocalDateTime now = LocalDateTime.now();
                    // the second-of-minute, from 0 to 59
                    int second = now.getSecond();
                    int sleepSeconds = (59 - second);
                    log.warn("请求达到每分钟限制，sleep：{} s", sleepSeconds);
                    TimeUnit.SECONDS.sleep(sleepSeconds);
                }
                // 重试当前页
                continue;
            }

            if (response == null || response.getList() == null) {
                log.warn("[syncBatch] accountId={} page={} 未返回日报数据", accountId, currentPage);
                break;
            }

            List<ReportStruct> pageList = response.getList();
            log.info("[syncBatch] accountId={} page={} 获取 {} 条数据", accountId, currentPage, pageList.size());

            // 立即处理当前页数据
            if (!pageList.isEmpty()) {
                int updated = processAndUpdatePage(pageList, accountId, creativeIdSet, processedCreativeIds, isReqTime);
                totalUpdated += updated;
            }

            // 判断是否有更多数据：当前页数据量小于pageSize表示已到最后一页
            if (pageList.size() < pageSize) {
                hasMore = false;
            } else {
                page++;
            }
        }

        // 所有分页完成后，对无报表数据的creativeId填充0值
        int zeroFilledCount = fillZeroForMissingCreatives(accountId, creativeIdSet, processedCreativeIds, isReqTime);
        totalUpdated += zeroFilledCount;

        log.info("[syncBatch] accountId={} 总共更新 {} 条 timeLine={}", accountId, totalUpdated, timeLine);
        return totalUpdated;
    }

    /**
     * 对无报表数据的creativeId填充0值
     */
    private int fillZeroForMissingCreatives(Long accountId, Set<Long> creativeIdSet,
                                            Set<Long> processedCreativeIds, boolean isReqTime) {
        List<AdPlanCreative> zeroUpdates = new ArrayList<>();
        creativeIdSet.forEach(creativeId -> {
            if (!processedCreativeIds.contains(creativeId)) {
                AdPlanCreative update = new AdPlanCreative();
                update.setCreativeId(creativeId);
                if (isReqTime) {
                    update.setCost(0L);
                    update.setViewCount(0L);
                    update.setValidClickCount(0L);
                    update.setCtr(BigDecimal.ZERO);
                    update.setConversionsCost(0L);
                    update.setReqIncomeVal1(0L);
                    update.setReqIncomeRoi1(BigDecimal.ZERO);
                    update.setScanFollowUserCost(0L);
                } else {
                    update.setRepIncomeVal1(0L);
                    update.setRepIncomeRoi1(BigDecimal.ZERO);
                }
                update.setUpdateTime(LocalDateTime.now());
                zeroUpdates.add(update);
            }
        });

        if (zeroUpdates.isEmpty()) {
            return 0;
        }

        // 批量更新0值
        if (isReqTime) {
            Lists.partition(zeroUpdates, 200).forEach(batch -> adPlanCreativeMapper.batchUpdateReqDailyReport(batch));
        } else {
            Lists.partition(zeroUpdates, 200).forEach(batch -> adPlanCreativeMapper.batchUpdateRepDailyReport(batch));
        }

        log.info("[fillZeroForMissingCreatives] accountId={} 填充0值 {} 条", accountId, zeroUpdates.size());
        return zeroUpdates.size();
    }

    /**
     * 处理单页数据并更新数据库
     */
    private int processAndUpdatePage(List<ReportStruct> pageList, Long accountId, Set<Long> creativeIdSet,
                                     Set<Long> processedCreativeIds, boolean isReqTime) {
        // 构建更新数据
        List<AdPlanCreative> toUpdate = new ArrayList<>();
        for (ReportStruct rs : pageList) {
            if (rs.getAdgroupId() == null) {
                continue;
            }
            Long creativeId = rs.getDynamicCreativeId();
            // 跳过已处理的creativeId
            if (processedCreativeIds.contains(creativeId)) {
                continue;
            }
            processedCreativeIds.add(creativeId);

            AdPlanCreative update = new AdPlanCreative();
            update.setCreativeId(creativeId);
            if (isReqTime) {
                // REQUEST_TIME 口径：更新 cost, view_count, valid_click_count, ctr, conversions_cost, req_income_val_1, req_income_roi_1
                update.setCost(rs.getCost());
                update.setViewCount(rs.getViewCount());
                update.setValidClickCount(rs.getValidClickCount());
                update.setCtr(d2b(rs.getCtr()));
                update.setConversionsCost(rs.getConversionsCost());
                update.setReqIncomeVal1(rs.getIncomeVal1());
                update.setReqIncomeRoi1(d2b(rs.getIncomeRoi1()));
                update.setScanFollowUserCost(rs.getScanFollowUserCost());
            } else {
                // REPORTING_TIME 口径：更新 rep_income_val_1, rep_income_roi_1
                update.setRepIncomeVal1(rs.getIncomeVal1());
                update.setRepIncomeRoi1(d2b(rs.getIncomeRoi1()));
            }
            update.setUpdateTime(LocalDateTime.now());
            toUpdate.add(update);
        }

        // 无报表数据的creativeId，填充0值（只在最后一页后处理，这里不处理）
        // 注意：0值填充逻辑移到所有分页完成后统一处理，避免中间页误填0值

        if (toUpdate.isEmpty()) {
            return 0;
        }

        // 批量更新
        if (isReqTime) {
            Lists.partition(toUpdate, 200).forEach(batch -> adPlanCreativeMapper.batchUpdateReqDailyReport(batch));
        } else {
            Lists.partition(toUpdate, 200).forEach(batch -> adPlanCreativeMapper.batchUpdateRepDailyReport(batch));
        }

        return toUpdate.size();
    }

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