package org.jeecg.modules.adweb.dmp.service.google;

import static org.jeecg.modules.adweb.dmp.dto.google.analytics.report.ReportConstant.*;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.annotations.VisibleForTesting;

import jakarta.annotation.PostConstruct;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.FastJsonUtil;
import org.jeecg.modules.adweb.common.mapper.CommonMapper;
import org.jeecg.modules.adweb.common.util.DateUtil;
import org.jeecg.modules.adweb.common.util.RestTemplateUtil;
import org.jeecg.modules.adweb.dmp.dto.OpenAPIRequest;
import org.jeecg.modules.adweb.dmp.dto.OpenAPIResponse;
import org.jeecg.modules.adweb.dmp.dto.google.analytics.GAPropertyDTO;
import org.jeecg.modules.adweb.dmp.dto.google.analytics.report.GAReportRequestDTO;
import org.jeecg.modules.adweb.dmp.dto.google.analytics.report.OrderByType;
import org.jeecg.modules.adweb.dmp.dto.google.analytics.report.ReportConstant;
import org.jeecg.modules.adweb.dmp.dto.google.analytics.report.ReportType;
import org.jeecg.modules.adweb.dmp.dto.google.analytics.report.data.CustomReportData;
import org.jeecg.modules.adweb.dmp.dto.google.analytics.report.data.GAReportDataDTO;
import org.jeecg.modules.adweb.dmp.entity.*;
import org.jeecg.modules.adweb.dmp.service.*;
import org.jeecg.modules.adweb.site.service.IAdwebSiteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * Google Analytics数据同步 - http://data-bridge.v3.adwebcloud.com:9002/swagger-ui/index.html
 *
 * @author wfansh
 */
@Slf4j
@Service
public class GAReportService {

    // GA report entities对应的数据表名称
    private static final String TABLE_GA_DAILY_REPORT =
            AnnotationUtils.findAnnotation(GADailyReport.class, TableName.class).value();
    private static final String TABLE_GA_COUNTRY_REPORT =
            AnnotationUtils.findAnnotation(GACountryReport.class, TableName.class).value();
    private static final String TABLE_GA_SOURCE_MEDIUM_REPORT =
            AnnotationUtils.findAnnotation(GASourceMediumReport.class, TableName.class).value();
    private static final String TABLE_GA_PAGE_PATH_REPORT =
            AnnotationUtils.findAnnotation(GAPagePathReport.class, TableName.class).value();

    private static final String GA_REPORT_API_PATH = "/api/google/ga/report";

    @Value("${data-bridge.api.host}")
    private String dataBridgeApiHost;

    @Value("${data-bridge.api.token}")
    private String dataBridgeApiToken;

    @Autowired private IAdwebSiteService adwebSiteService;
    @Autowired private IGoogleGTMService googleGTMService;
    @Autowired private IGADailyReportService gaDailyReportService;
    @Autowired private IGACountryReportService gaCountryReportService;
    @Autowired private IGASourceMediumReportService gaSourceMediumReportService;
    @Autowired private IGAPagePathReportService gaPagePathReportService;

    @Autowired private CommonMapper commonMapper;

    private RestTemplate restTemplate;

    @PostConstruct
    private void init() {
        this.restTemplate = RestTemplateUtil.getRestTemplate(60, 60, dataBridgeApiToken);
    }

    /**
     * 拉取并同步Google Analytics报表
     *
     * @param siteCodes 待执行的网站codes，为空时执行所有网站
     */
    public void syncGAReport(List<String> siteCodes) {
        if (CollectionUtils.isEmpty(siteCodes)) {
            siteCodes = adwebSiteService.getAllActiveSiteCodes();
        }

        List<GoogleGTM> googleGTMs =
                googleGTMService.list(
                        new LambdaQueryWrapper<GoogleGTM>().in(GoogleGTM::getSiteCode, siteCodes));

        for (GoogleGTM googleGTM : googleGTMs) {
            // 每个帐号同步更新四张报表
            try {
                this.syncGADailyReport(googleGTM);
                this.syncGACountryReport(googleGTM);
                this.syncGASourceMediumReport(googleGTM);
                this.syncGAPagePathReport(googleGTM);
            } catch (RuntimeException e) {
                log.warn("同步GA报表异常, siteCode = {], error = {}", googleGTM.getSiteCode(), e);
            }
        }
    }

    /**
     * 拉取并同步Google Analytics - Daily报表
     *
     * @param googleGTM
     */
    private void syncGADailyReport(GoogleGTM googleGTM) {
        // 1. 报表时间区间
        Date startDate = this.getReportStartDate(TABLE_GA_DAILY_REPORT, googleGTM.getSiteCode());
        Date endDate = new Date();

        // 2. 构建GA报表请求参数
        // 使用ADWEB_CUSTOM_REPORT，不是ADWEB_DATE_VIEW - 需要更多metrics字段
        GAReportRequestDTO gaReportRequest = new GAReportRequestDTO();
        gaReportRequest.setPropertyResourceName(
                GAPropertyDTO.toResourceName(googleGTM.getGaPropertyId()));
        gaReportRequest.setReportType(ReportType.ADWEB_CUSTOM_REPORT);
        gaReportRequest.setStartDate(DateUtils.date2Str(startDate, DateUtils.date_sdf.get()));
        gaReportRequest.setEndDate(DateUtils.date2Str(endDate, DateUtils.date_sdf.get()));
        gaReportRequest.setMetrics(
                List.of(
                        ReportConstant.METRIC_TOTAL_USERS,
                        ReportConstant.METRIC_NEW_USERS,
                        ReportConstant.METRIC_SESSIONS,
                        ReportConstant.METRIC_BOUNCE_RATE,
                        ReportConstant.METRIC_AVG_SESSION_DURATION,
                        ReportConstant.METRIC_SCREEN_PAGE_VIEWS,
                        ReportConstant.METRIC_SCREEN_PAGE_VIEWS_PER_SESSION));
        gaReportRequest.setDimensions(List.of(ReportConstant.DIMENSION_DATE));
        gaReportRequest.setOrderByType(OrderByType.DIMENSIONS);
        gaReportRequest.setOrderBy(ReportConstant.DIMENSION_DATE);
        List<CustomReportData> reportDataList =
                runGAReport(gaReportRequest, CustomReportData.class);

        // 2. 转化为DB entity
        List<GADailyReport> dailyReport = Lists.newArrayList();
        for (CustomReportData reportData : reportDataList) {
            GADailyReport reportRow = new GADailyReport();
            reportRow.setSiteCode(googleGTM.getSiteCode());
            reportRow.setDate(
                    DateUtils.str2Date(
                            reportData.get(ReportConstant.DIMENSION_DATE),
                            DateUtils.date_sdf.get()));
            reportRow.setTotalUsers(
                    Integer.parseInt(reportData.get(ReportConstant.METRIC_TOTAL_USERS)));
            reportRow.setNewUsers(
                    Integer.parseInt(reportData.get(ReportConstant.METRIC_NEW_USERS)));
            reportRow.setSessions(Integer.parseInt(reportData.get(ReportConstant.METRIC_SESSIONS)));
            reportRow.setBounceRate(
                    Double.parseDouble(reportData.get(ReportConstant.METRIC_BOUNCE_RATE)));
            reportRow.setAvgSessionDuration(
                    Double.parseDouble(reportData.get(ReportConstant.METRIC_AVG_SESSION_DURATION)));
            reportRow.setPageViews(Integer.parseInt(reportData.get(METRIC_SCREEN_PAGE_VIEWS)));
            reportRow.setPageViewsPerSession(
                    Double.parseDouble(
                            reportData.get(ReportConstant.METRIC_SCREEN_PAGE_VIEWS_PER_SESSION)));

            dailyReport.add(reportRow);
        }

        // 3. 更新数据库 - 删除旧数据，插入新数据
        gaDailyReportService.remove(
                this.getRemoveQueryWrapper(
                        GADailyReport.class, googleGTM.getSiteCode(), startDate, endDate));

        gaDailyReportService.saveBatch(dailyReport, dailyReport.size());
    }

    /**
     * 拉取并同步Google Analytics - Country报表
     *
     * @param googleGTM
     */
    private void syncGACountryReport(GoogleGTM googleGTM) {
        // 1. 报表时间区间
        Date startDate = this.getReportStartDate(TABLE_GA_COUNTRY_REPORT, googleGTM.getSiteCode());
        Date endDate = new Date();

        // 2. 构建GA报表请求参数
        // 使用ADWEB_CUSTOM_REPORT，不是ADWEB_COUNTRY_CHART - 需要date作为dimension
        GAReportRequestDTO gaReportRequest = new GAReportRequestDTO();
        gaReportRequest.setPropertyResourceName(
                GAPropertyDTO.toResourceName(googleGTM.getGaPropertyId()));
        gaReportRequest.setReportType(ReportType.ADWEB_CUSTOM_REPORT);
        gaReportRequest.setStartDate(DateUtils.date2Str(startDate, DateUtils.date_sdf.get()));
        gaReportRequest.setEndDate(DateUtils.date2Str(endDate, DateUtils.date_sdf.get()));
        gaReportRequest.setMetrics(List.of(ReportConstant.METRIC_TOTAL_USERS));
        gaReportRequest.setDimensions(
                List.of(ReportConstant.DIMENSION_DATE, ReportConstant.DIMENSION_COUNTRY));
        gaReportRequest.setOrderByType(OrderByType.DIMENSIONS);
        gaReportRequest.setOrderBy(ReportConstant.DIMENSION_DATE);
        List<CustomReportData> reportDataList =
                runGAReport(gaReportRequest, CustomReportData.class);

        // 2. 转化为DB entity
        List<GACountryReport> countryReport = Lists.newArrayList();
        for (CustomReportData reportData : reportDataList) {
            GACountryReport reportRow = new GACountryReport();
            reportRow.setSiteCode(googleGTM.getSiteCode());
            reportRow.setDate(
                    DateUtils.str2Date(
                            reportData.get(ReportConstant.DIMENSION_DATE),
                            DateUtils.date_sdf.get()));
            reportRow.setCountry(reportData.get(ReportConstant.DIMENSION_COUNTRY));
            reportRow.setTotalUsers(
                    Integer.parseInt(reportData.get(ReportConstant.METRIC_TOTAL_USERS)));

            countryReport.add(reportRow);
        }

        // 3. 更新数据库 - 删除旧数据，插入新数据
        gaCountryReportService.remove(
                this.getRemoveQueryWrapper(
                        GACountryReport.class, googleGTM.getSiteCode(), startDate, endDate));

        gaCountryReportService.saveBatch(countryReport, countryReport.size());
    }

    /**
     * 拉取并同步Google Analytics - Session Source Medium报表
     *
     * @param googleGTM
     */
    private void syncGASourceMediumReport(GoogleGTM googleGTM) {
        // 1. 报表时间区间
        Date startDate =
                this.getReportStartDate(TABLE_GA_SOURCE_MEDIUM_REPORT, googleGTM.getSiteCode());
        Date endDate = new Date();

        // 2. 构建GA报表请求参数
        // 使用ADWEB_CUSTOM_REPORT，不是ADWEB_SESSION_SOURCE_MEDIUM_VIEW - 需要date作为dimension
        GAReportRequestDTO gaReportRequest = new GAReportRequestDTO();
        gaReportRequest.setPropertyResourceName(
                GAPropertyDTO.toResourceName(googleGTM.getGaPropertyId()));
        gaReportRequest.setReportType(ReportType.ADWEB_CUSTOM_REPORT);
        gaReportRequest.setStartDate(DateUtils.date2Str(startDate, DateUtils.date_sdf.get()));
        gaReportRequest.setEndDate(DateUtils.date2Str(endDate, DateUtils.date_sdf.get()));
        gaReportRequest.setMetrics(
                List.of(
                        ReportConstant.METRIC_TOTAL_USERS,
                        ReportConstant.METRIC_NEW_USERS,
                        ReportConstant.METRIC_SESSIONS,
                        ReportConstant.METRIC_BOUNCE_RATE,
                        ReportConstant.METRIC_AVG_SESSION_DURATION,
                        ReportConstant.METRIC_SCREEN_PAGE_VIEWS_PER_SESSION));
        gaReportRequest.setDimensions(
                List.of(
                        ReportConstant.DIMENSION_DATE,
                        ReportConstant.DIMENSION_SESSION_SOURCE_MEDIUM));
        gaReportRequest.setOrderByType(OrderByType.DIMENSIONS);
        gaReportRequest.setOrderBy(ReportConstant.DIMENSION_DATE);
        List<CustomReportData> reportDataList =
                runGAReport(gaReportRequest, CustomReportData.class);

        // 2. 转化为DB entity
        List<GASourceMediumReport> sourceMediumReport = Lists.newArrayList();
        for (CustomReportData reportData : reportDataList) {
            GASourceMediumReport reportRow = new GASourceMediumReport();
            reportRow.setSiteCode(googleGTM.getSiteCode());
            reportRow.setDate(
                    DateUtils.str2Date(
                            reportData.get(ReportConstant.DIMENSION_DATE),
                            DateUtils.date_sdf.get()));
            reportRow.setType(reportData.get(ReportConstant.DIMENSION_SESSION_SOURCE_MEDIUM));
            reportRow.setTotalUsers(
                    Integer.parseInt(reportData.get(ReportConstant.METRIC_TOTAL_USERS)));
            reportRow.setNewUsers(
                    Integer.parseInt(reportData.get(ReportConstant.METRIC_NEW_USERS)));
            reportRow.setSessions(Integer.parseInt(reportData.get(ReportConstant.METRIC_SESSIONS)));
            reportRow.setBounceRate(
                    Double.parseDouble(reportData.get(ReportConstant.METRIC_BOUNCE_RATE)));
            reportRow.setAvgSessionDuration(
                    Double.parseDouble(reportData.get(ReportConstant.METRIC_AVG_SESSION_DURATION)));
            reportRow.setPageViewsPerSession(
                    Double.parseDouble(
                            reportData.get(ReportConstant.METRIC_SCREEN_PAGE_VIEWS_PER_SESSION)));

            sourceMediumReport.add(reportRow);
        }

        // 3. 更新数据库 - 删除旧数据，插入新数据
        gaSourceMediumReportService.remove(
                this.getRemoveQueryWrapper(
                        GASourceMediumReport.class, googleGTM.getSiteCode(), startDate, endDate));

        gaSourceMediumReportService.saveBatch(sourceMediumReport, sourceMediumReport.size());
    }

    /**
     * 拉取并同步Google Analytics - Page Path报表
     *
     * @param googleGTM
     */
    private void syncGAPagePathReport(GoogleGTM googleGTM) {
        // 1. 报表时间区间
        Date startDate =
                this.getReportStartDate(TABLE_GA_PAGE_PATH_REPORT, googleGTM.getSiteCode());
        Date endDate = new Date();

        // 2. 构建GA报表请求参数
        // 使用ADWEB_CUSTOM_REPORT，不是ADWEB_PAGE_PATH_VIEW - 需要date作为dimension
        GAReportRequestDTO gaReportRequest = new GAReportRequestDTO();
        gaReportRequest.setPropertyResourceName(
                GAPropertyDTO.toResourceName(googleGTM.getGaPropertyId()));
        gaReportRequest.setReportType(ReportType.ADWEB_CUSTOM_REPORT);
        gaReportRequest.setStartDate(DateUtils.date2Str(startDate, DateUtils.date_sdf.get()));
        gaReportRequest.setEndDate(DateUtils.date2Str(endDate, DateUtils.date_sdf.get()));
        gaReportRequest.setMetrics(
                List.of(
                        ReportConstant.METRIC_ENGAGEMENT_RATE,
                        ReportConstant.METRIC_SCREEN_PAGE_VIEWS,
                        ReportConstant.METRIC_USER_ENGAGEMENT_DURATION));
        gaReportRequest.setDimensions(
                List.of(ReportConstant.DIMENSION_DATE, ReportConstant.DIMENSION_PAGE_PATH));
        gaReportRequest.setOrderByType(OrderByType.DIMENSIONS);
        gaReportRequest.setOrderBy(ReportConstant.DIMENSION_DATE);
        List<CustomReportData> reportDataList =
                runGAReport(gaReportRequest, CustomReportData.class);

        // 2. 转化为DB entity
        List<GAPagePathReport> pagePathReport = Lists.newArrayList();
        for (CustomReportData reportData : reportDataList) {
            GAPagePathReport reportRow = new GAPagePathReport();
            reportRow.setSiteCode(googleGTM.getSiteCode());
            reportRow.setDate(
                    DateUtils.str2Date(
                            reportData.get(ReportConstant.DIMENSION_DATE),
                            DateUtils.date_sdf.get()));
            reportRow.setPagePath(reportData.get(DIMENSION_PAGE_PATH));
            reportRow.setEngagementRate(
                    Double.parseDouble(reportData.get(ReportConstant.METRIC_ENGAGEMENT_RATE)));
            reportRow.setPageViews(
                    Integer.parseInt(reportData.get(ReportConstant.METRIC_SCREEN_PAGE_VIEWS)));
            reportRow.setAvgTimeOnPage(
                    Double.parseDouble(
                            reportData.get(ReportConstant.METRIC_USER_ENGAGEMENT_DURATION)));

            pagePathReport.add(reportRow);
        }

        // 3. 更新数据库 - 删除旧数据，插入新数据
        gaPagePathReportService.remove(
                this.getRemoveQueryWrapper(
                        GAPagePathReport.class, googleGTM.getSiteCode(), startDate, endDate));

        gaPagePathReportService.saveBatch(pagePathReport, pagePathReport.size());
    }

    /**
     * 请求Google Analytics报表，http://data-bridge.v3.adwebcloud.com:9002/swagger-ui/index.html
     *
     * @param gaReportRequest
     * @param reportDataClass
     * @return
     * @param <T>
     */
    @VisibleForTesting
    <T extends GAReportDataDTO> List<T> runGAReport(
            GAReportRequestDTO gaReportRequest, Class<T> reportDataClass) {
        log.info("runGAReport: report request = {}", FastJsonUtil.toJSONString(gaReportRequest));

        // 1. 创建API request
        OpenAPIRequest<GAReportRequestDTO> openAPIRequest = new OpenAPIRequest<>();
        openAPIRequest.setRequestServer(this.getClass().getSimpleName());
        openAPIRequest.setRequestTime(System.currentTimeMillis());
        openAPIRequest.setData(gaReportRequest);

        // 2. 请求API接口
        OpenAPIResponse<List<T>> openAPIResponse =
                RestTemplateUtil.postForObject(
                        restTemplate,
                        dataBridgeApiHost + GA_REPORT_API_PATH,
                        openAPIRequest,
                        ParameterizedTypeReference.forType(
                                TypeUtils.parameterize(
                                        OpenAPIResponse.class,
                                        TypeUtils.parameterize(List.class, reportDataClass))));

        // 3. 日志并返回
        log.info(
                "runGAReport: report response = {}",
                FastJsonUtil.toJSONString(openAPIResponse.getData()));
        return openAPIResponse.getData();
    }

    /**
     * 计算GA报表开始时间
     *
     * <p>1. 若表中没有数据，取一年前
     *
     * <p>2. 表中最大时间减一天 - 如10月10号凌晨2点执行，最大时间可能是10号；同时9号数据GA侧有可能更新
     */
    private Date getReportStartDate(String tableName, String siteCode) {
        Date maxDate =
                commonMapper.getMaxDate(
                        tableName, "date", String.format("site_code = '%s'", siteCode));
        if (Objects.isNull(maxDate)) {
            // 1. 一年前
            return DateUtil.addDays(new Date(), -365);
        }
        // 2. 最大时间减一天
        return DateUtil.addDays(maxDate, -1);
    }

    /** 生成{@link QueryWrapper}， 删除GA报表历史数据 */
    private <T> QueryWrapper<T> getRemoveQueryWrapper(
            Class<T> entityClass, String siteCode, Date startDate, Date endDate) {
        QueryWrapper<T> queryWrapper = new QueryWrapper<>(entityClass);
        queryWrapper.eq("site_code", siteCode);
        queryWrapper.ge("date", startDate);
        queryWrapper.le("date", endDate);
        return queryWrapper;
    }
}
