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.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

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.AdPlan;
import com.moka.gdtauto.entity.AdPlanConf;
import com.moka.gdtauto.entity.BaseDailyAdgroupReport;
import com.moka.gdtauto.entity.RepDailyAdgroupReport;
import com.moka.gdtauto.entity.ReqDailyAdgroupReport;
import com.moka.gdtauto.mapper.RepDailyAdgroupReportMapper;
import com.moka.gdtauto.mapper.ReqDailyAdgroupReportMapper;
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 DailyAdgroupReportService {

    private final TencentAdsApiClientFactory clientFactory;
    private final ReqDailyAdgroupReportMapper reqMapper;
    private final RepDailyAdgroupReportMapper repMapper;
    private final AdPlanConfService adPlanConfService;
    private final AdPlanService adPlanService;

    /** 同步时请求的指标字段 */
    private static final List<String> SYNC_FIELDS = Arrays.asList(
            "account_id",
            "adgroup_id", "adgroup_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"
    );

    /**
     * 按广告计划配置同步广告组日报
     */
    public int syncReport(AdPlanConf conf, String startDate, String endDate, String timeLine) throws Exception {
        log.info("开始同步广告组日报 confId={} timeLine={} [{}, {}]", conf.getId(), 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("adgroup_id", "date");
        int pageNum = 1;
        int pageSize = 100;
        int totalSaved = 0;

        while (true) {
            Page<AdPlan> page = adPlanService.page(
                    new Page<>(pageNum, pageSize),
                    new LambdaQueryWrapper<AdPlan>()
                            .eq(AdPlan::getAdPlanConfId, conf.getId())
                            .eq(AdPlan::getStatus, 4)
                            .eq(AdPlan::getSystemDelete, "false"));

            List<AdPlan> planList = page.getRecords();
            if (planList.isEmpty()) {
                break;
            }

            Map<Long, List<Long>> accountAdgroupIdsMap = planList.stream()
                    .filter(plan -> plan.getAccountId() != null && plan.getAdgroupId() != null)
                    .collect(Collectors.groupingBy(
                            AdPlan::getAccountId,
                            Collectors.mapping(
                                    AdPlan::getAdgroupId,
                                    Collectors.collectingAndThen(Collectors.toList(),
                                            ids -> ids.stream().distinct().toList()))));

            for (Map.Entry<Long, List<Long>> entry : accountAdgroupIdsMap.entrySet()) {
                totalSaved += syncAccountReports(
                        tencentAds,
                        conf.getId(),
                        conf.getAccountGroupId(),
                        entry.getKey(),
                        entry.getValue(),
                        dateRange,
                        groupBy,
                        timeLine);
            }

            if (!page.hasNext()) {
                break;
            }
            pageNum++;
        }

        log.info("广告组日报同步完成 confId={} saved={}", conf.getId(), totalSaved);
        return totalSaved;
    }

    /**
     * 批量同步广告组日报
     */
    public int 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 0;
        }

        AtomicInteger total = new AtomicInteger();
        Semaphore semaphore = new Semaphore(10);

        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (AdPlanConf conf : confList) {
                executor.submit(() -> {
                    try {
                        semaphore.acquire();
                        total.addAndGet(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("所有广告组日报同步完成 total={}", total.get());
        return total.get();
    }

    private int syncAccountReports(TencentAds tencentAds,
                                    Long confId,
                                   Long groupId,
                                   Long accountId,
                                   List<Long> adgroupIds,
                                   ReportDateRange dateRange,
                                   List<String> groupBy,
                                   String timeLine) throws Exception {
        if (adgroupIds == null || adgroupIds.isEmpty()) {
            return 0;
        }

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

        long page = 1L;
        long pageSize = 1000L;
        int totalSaved = 0;

        while (true) {
            DailyReportsGetResponseData response = tencentAds.dailyReports().dailyReportsGet(
                    "REPORT_LEVEL_ADGROUP",
                    dateRange,
                    groupBy,
                    SYNC_FIELDS,
                    accountId,
                    filtering,
                    null,
                    timeLine,
                    page,
                    pageSize,
                    null);

            if (response == null || response.getList() == null || response.getList().isEmpty()) {
                break;
            }

            List<ReportStruct> list = response.getList();
            if (TimeLine.REQUEST_TIME.getValue().equals(timeLine)) {
                List<ReqDailyAdgroupReport> entities = new ArrayList<>();
                for (ReportStruct rs : list) {
                    if (rs.getAdgroupId() == null || rs.getDate() == null) {
                        continue;
                    }
                    entities.add(toReqEntity(confId, groupId, rs));
                }
                if (!entities.isEmpty()) {
                    reqMapper.batchUpsert(entities);
                    totalSaved += entities.size();
                }
            } else {
                List<RepDailyAdgroupReport> entities = new ArrayList<>();
                for (ReportStruct rs : list) {
                    if (rs.getAdgroupId() == null || rs.getDate() == null) {
                        continue;
                    }
                    entities.add(toRepEntity(confId, groupId, rs));
                }
                if (!entities.isEmpty()) {
                    repMapper.batchUpsert(entities);
                    totalSaved += entities.size();
                }
            }

            long totalNumber = response.getPageInfo() != null
                    ? response.getPageInfo().getTotalNumber() : 0L;
            if (page * pageSize >= totalNumber) {
                break;
            }
            page++;
        }

        return totalSaved;
    }

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

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

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

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

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

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

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

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

    private void fillFields(BaseDailyAdgroupReport e, Long confId, Long groupId, ReportStruct rs) {
        e.setConfId(confId);
        e.setAccountId(rs.getAccountId());
        e.setGroupId(groupId);
        e.setAdgroupId(rs.getAdgroupId());
        e.setAdgroupName(rs.getAdgroupName());
        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
        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);
    }
}
