// 社交 BI v2 的数据层：负责 master/overview/funnel/kpi 接口的拉取与派生行，
// 视图不直接调 API，只消费这里的产物（原始 snake_case 指标行 + 维度字段）。

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { fetchSocialBiFunnel, fetchSocialBiKpi, fetchSocialBiMaster, fetchSocialBiOverview, fetchSocialBiRequirements } from "../api.js";
import { DEFAULT_TIME_ZONE, rangeExclusiveEndMs, rangeStartMs } from "../utils/time.js";
import { ALL, appSelectionMatches, regionSelectionMatches, resolveDateRange } from "./state.js";

export const SOCIAL_BI_TZ = DEFAULT_TIME_ZONE;
export const SOCIAL_BI_FUNNEL_APP_CODES = ["lalu", "huwaa", "fami"];

export function useSocialBiData(filters, { includeFunnel = false, includeKpi = false } = {}) {
  const [master, setMaster] = useState(null);
  // 数据请求等 master 结果落地后再发：否则会先用兜底 App 列表打一轮，master 到达后立刻重打一轮（整轮浪费）。
  const [isMasterSettled, setIsMasterSettled] = useState(false);
  const [overview, setOverview] = useState(null);
  const [funnel, setFunnel] = useState(null);
  const [kpi, setKpi] = useState(null);
  const [requirements, setRequirements] = useState(null);
  const [requirementsError, setRequirementsError] = useState("");
  const [isRequirementsLoading, setIsRequirementsLoading] = useState(false);
  const [isLoading, setIsLoading] = useState(true);
  const [errors, setErrors] = useState([]);
  const [refreshToken, setRefreshToken] = useState(0);
  const requestSeq = useRef(0);
  const requirementsSeq = useRef(0);
  const requirementsAbort = useRef(null);

  useEffect(() => {
    let ignore = false;
    fetchSocialBiMaster()
      .then((data) => {
        if (!ignore && data) {
          setMaster(data);
        }
      })
      .catch(() => {
        if (!ignore) {
          setMaster(null);
        }
      })
      .finally(() => {
        if (!ignore) {
          setIsMasterSettled(true);
        }
      });
    return () => {
      ignore = true;
    };
  }, []);

  useEffect(
    () => () => {
      // 页面离开后主动取消尚未完成的宽表查询，让取消信号沿 admin request context
      // 传到 statistics-service/MySQL，避免不可见请求继续占用数据库连接。
      requirementsSeq.current++;
      requirementsAbort.current?.abort();
    },
    []
  );

  // range 只依赖日期相关筛选：切换粒度/区域/App 多选不应触发接口重拉（那些是纯前端派生）。
  const { customEnd, customStart, preset } = filters;
  const range = useMemo(
    () => resolveDateRange({ customEnd, customStart, preset }, SOCIAL_BI_TZ),
    [customEnd, customStart, preset]
  );
  const appCodes = useMemo(() => {
    const available = (master?.apps || []).map((app) => app.app_code);
    if (!filters.apps.length || filters.apps.includes(ALL)) {
      return available;
    }
    return filters.apps.filter((appCode) => !available.length || available.includes(appCode));
  }, [filters.apps, master]);
  const funnelAppCodes = useMemo(() => {
    const available = (master?.apps || []).map((app) => app.app_code).filter((appCode) => SOCIAL_BI_FUNNEL_APP_CODES.includes(appCode));
    if (!filters.apps.length || filters.apps.includes(ALL)) {
      return available;
    }
    const selected = filters.apps.filter((appCode) => available.includes(appCode));
    return selected.length ? selected : available;
  }, [filters.apps, master]);
  // 运营人员筛选只接受 master 目录中的正整数 ID；URL 中的脏值不能直接进入后端统计查询。
  const operatorUserId = useMemo(() => {
    const value = String(filters.operator || "");
    return /^\d+$/.test(value) ? Number(value) : undefined;
  }, [filters.operator]);
  const overviewRequestScopes = useMemo(
    () => buildOverviewRequestScopes(appCodes, filters.regions, master?.regions || []),
    [appCodes, filters.regions, master?.regions]
  );

  useEffect(() => {
    if (!isMasterSettled) {
      return;
    }
    const sequence = ++requestSeq.current;
    const startMs = rangeStartMs(range, SOCIAL_BI_TZ);
    const endMs = rangeExclusiveEndMs(range, SOCIAL_BI_TZ);
    setIsLoading(true);
    // 漏斗会执行 cohort 聚合，只在用户进入漏斗视图时请求；其他视图不能为不可见数据承担查询成本或错误提示。
    const funnelRequest = includeFunnel
      ? fetchSocialBiFunnel({ appCodes: funnelAppCodes, endMs, startMs, statTz: SOCIAL_BI_TZ })
      : Promise.resolve(null);
    // KPI 会执行当前区间和自然月两组负责范围聚合，只在运营中心可见时请求，避免其他视图承担额外查询成本。
    const kpiRequest = includeKpi
      ? fetchSocialBiKpi({ appCodes, endMs, operatorUserId, startMs, statTz: SOCIAL_BI_TZ })
      : Promise.resolve(null);
    // 默认全量只请求一次；用户显式选择区域时按 App 下发已选区域，让后端只补这些区域的 Finance 逐日渠道。
    const overviewRequest = overviewRequestScopes.length
      ? Promise.all(
          overviewRequestScopes.map((scope) =>
            fetchSocialBiOverview({
              ...scope,
              endMs,
              startMs,
              statTz: SOCIAL_BI_TZ
            })
          )
        ).then(mergeOverviewResponses)
      : Promise.resolve({ access: master?.access || null, apps: [] });
    Promise.allSettled([overviewRequest, funnelRequest, kpiRequest]).then(([overviewResult, funnelResult, kpiResult]) => {
      if (sequence !== requestSeq.current) {
        return;
      }
      const nextErrors = [];
      if (overviewResult.status === "fulfilled") {
        setOverview(overviewResult.value);
        (overviewResult.value?.apps || []).forEach((app) => {
          if (app.error) {
            nextErrors.push(`${app.app_name || app.app_code}: ${app.error}`);
          }
        });
      } else {
        setOverview(null);
        nextErrors.push(`统计数据加载失败: ${overviewResult.reason?.message || "未知错误"}`);
      }
      if (!includeFunnel) {
        setFunnel(null);
      } else if (funnelResult.status === "fulfilled") {
        setFunnel(funnelResult.value);
        (funnelResult.value?.apps || []).forEach((app) => {
          if (app.error) {
            nextErrors.push(`${app.app_name || app.app_code}: ${app.error}`);
          }
        });
      } else {
        setFunnel(null);
        nextErrors.push(`埋点漏斗加载失败: ${funnelResult.reason?.message || "未知错误"}`);
      }
      if (!includeKpi) {
        setKpi(null);
      } else if (kpiResult.status === "fulfilled") {
        setKpi(kpiResult.value);
        Object.entries(kpiResult.value?.app_errors || {}).forEach(([appCode, message]) => {
          nextErrors.push(`${appCode}: ${message}`);
        });
      } else {
        setKpi(null);
        nextErrors.push(`运营充值数据加载失败: ${kpiResult.reason?.message || "未知错误"}`);
      }
      setErrors(nextErrors);
      setIsLoading(false);
    });
  }, [appCodes, funnelAppCodes, includeFunnel, includeKpi, isMasterSettled, master?.access, operatorUserId, overviewRequestScopes, range, refreshToken]);

  const loadRequirements = useCallback(
    ({ newUserType, payerType, section, userRole } = {}) => {
      if (!isMasterSettled) {
        return Promise.resolve(null);
      }
      const sequence = ++requirementsSeq.current;
      const startMs = rangeStartMs(range, SOCIAL_BI_TZ);
      const endMs = rangeExclusiveEndMs(range, SOCIAL_BI_TZ);
      const requestScopes = buildRequirementRequestScopes(appCodes, filters.regions);
      // 筛选、分组或刷新变化时，旧请求的数据已不可能再被使用；seq 防止旧响应写回，
      // AbortController 则真正终止旧 HTTP/DB 查询，二者分别解决正确性和资源占用。
      requirementsAbort.current?.abort();
      requirementsAbort.current = null;
      setIsRequirementsLoading(true);
      setRequirementsError("");
      setRequirements(null);
      if (!requestScopes.length) {
        const emptyResult = { access: master?.access || null, apps: [] };
        setRequirements(emptyResult);
        setIsRequirementsLoading(false);
        return Promise.resolve(emptyResult);
      }
      const controller = new AbortController();
      requirementsAbort.current = controller;
      // 数据需求按表格页局部 section/角色/付费筛选请求；全局 App/日期仍复用 Social BI 顶栏口径。
      return Promise.all(requestScopes.map((scope) => fetchSocialBiRequirements({
        ...scope,
        endMs,
        newUserType,
        payerType,
        section,
        signal: controller.signal,
        startMs,
        statTz: SOCIAL_BI_TZ,
        userRole
      })))
        .then((results) => {
          if (sequence !== requirementsSeq.current) {
            return null;
          }
          const data = mergeRequirementResponses(results);
          setRequirements(data);
          return data;
        })
        .catch((error) => {
          if (error?.name === "AbortError") {
            return null;
          }
          if (sequence === requirementsSeq.current) {
            setRequirements(null);
            setRequirementsError(error?.message || "数据需求加载失败");
          }
          return null;
        })
        .finally(() => {
          if (requirementsAbort.current === controller) {
            requirementsAbort.current = null;
          }
          if (sequence === requirementsSeq.current) {
            setIsRequirementsLoading(false);
          }
        });
    },
    [appCodes, filters.regions, isMasterSettled, master?.access, range]
  );

  const refresh = useCallback(() => {
    setRefreshToken((current) => current + 1);
  }, []);

  const derived = useMemo(() => deriveRows(overview, filters, master), [filters, master, overview]);

  return {
    appCodes,
    derived,
    errors,
    funnel,
    funnelAppCodes,
    isLoading,
    isRequirementsLoading,
    kpi,
    loadRequirements,
    master,
    overview,
    range,
    refreshToken,
    requirements,
    requirementsError,
    refresh
  };
}

// deriveRows 输出四类行，全部带维度字段 + 原始指标字段：
// appTotals/appDaily（App 粒度）、regionTotals/regionDaily（大区粒度，已按顶栏区域选择过滤）、
// countryTotals（国家粒度，已按国家→区域目录归一 region_id 并跟随区域筛选）。restricted=true 表示该 App 已被数据范围裁剪。
function deriveRows(overview, filters, master) {
  const appTotals = [];
  const appDaily = [];
  const regionTotals = [];
  const regionDaily = [];
  const countryTotals = [];
  const appErrors = [];
  const regionByCountry = buildRegionByCountryIndex(master);

  (overview?.apps || []).forEach((app) => {
    if (!appSelectionMatches(filters.apps, app.app_code)) {
      return;
    }
    const context = {
      app_code: app.app_code,
      app_name: app.app_name || app.app_code,
      kind: app.kind,
      restricted: Boolean(app.restricted)
    };
    if (app.error) {
      appErrors.push({ ...context, error: app.error });
      return;
    }
    appTotals.push({ ...context, ...(app.total || {}) });
    (app.daily_series || []).forEach((row) => {
      appDaily.push({ ...context, ...row, stat_day: row.stat_day || row.label });
    });
    (app.region_breakdown || []).forEach((row) => {
      if (!regionSelectionMatches(filters.regions, app.app_code, Number(row.region_id ?? 0))) {
        return;
      }
      regionTotals.push({
        ...context,
        ...row,
        region_id: Number(row.region_id ?? 0),
        region_name: row.region_name || row.region_code || `区域 ${row.region_id}`
      });
    });
    (app.daily_region_breakdown || []).forEach((row) => {
      if (!regionSelectionMatches(filters.regions, app.app_code, Number(row.region_id ?? 0))) {
        return;
      }
      regionDaily.push({
        ...context,
        ...row,
        region_id: Number(row.region_id ?? 0),
        region_name: row.region_name || row.region_code || `区域 ${row.region_id}`,
        stat_day: row.stat_day || row.label
      });
    });
    (app.country_breakdown || []).forEach((row) => {
      const countryCode = row.country_code || String(row.country_id || "");
      // legacy 国家行不带 region_id，用区域目录（countries 列表）归一，保证下钻与区域筛选口径一致。
      let regionId = Number(row.region_id ?? 0);
      let regionName = row.region_name || "";
      if (!regionId) {
        const region = regionByCountry.get(`${app.app_code}:${String(countryCode).toUpperCase()}`);
        if (region) {
          regionId = region.region_id;
          regionName = region.region_name || region.region_code || regionName;
        }
      }
      if (!regionSelectionMatches(filters.regions, app.app_code, regionId)) {
        return;
      }
      countryTotals.push({
        ...context,
        ...row,
        country_code: countryCode,
        country_name: row.country_name || row.country || row.country_code || `国家 ${row.country_id}`,
        region_id: regionId,
        region_name: regionName
      });
    });
  });

  // 顶栏选了区域时，App 合计/日行切换成所选区域行的聚合视角由各视图用 regionScoped 判断。
  const regionScoped = Boolean(filters.regions.length && !filters.regions.includes(ALL));
  return { appDaily, appErrors, appTotals, countryTotals, regionDaily, regionScoped, regionTotals };
}

function buildRegionByCountryIndex(master) {
  const index = new Map();
  (master?.regions || []).forEach((region) => {
    (region.countries || []).forEach((countryCode) => {
      const key = `${region.app_code}:${String(countryCode).toUpperCase()}`;
      if (!index.has(key)) {
        index.set(key, region);
      }
    });
  });
  return index;
}

function buildRequirementRequestScopes(appCodes, regions) {
  const selectedApps = new Set((appCodes || []).filter(Boolean));
  if (!regions?.length || regions.includes(ALL)) {
    return [{ appCodes: [...selectedApps] }];
  }
  const byApp = new Map();
  regions.forEach((selection) => {
    const value = String(selection || "").trim();
    if (!value || value === ALL) {
      return;
    }
    if (value.startsWith("app:")) {
      const appCode = value.slice(4).trim().toLowerCase();
      if (appCode) {
        byApp.set(appCode, null);
      }
      return;
    }
    const [rawAppCode, rawRegionID] = value.split(":");
    const appCode = String(rawAppCode || "").trim().toLowerCase();
    const regionID = Number(rawRegionID);
    if (!appCode || !Number.isFinite(regionID) || regionID <= 0 || byApp.get(appCode) === null) {
      return;
    }
    if (!byApp.has(appCode)) {
      byApp.set(appCode, new Set());
    }
    byApp.get(appCode).add(regionID);
  });
  const scopes = [];
  byApp.forEach((regionIDs, appCode) => {
    if (selectedApps.size && !selectedApps.has(appCode)) {
      return;
    }
    if (regionIDs === null) {
      scopes.push({ appCodes: [appCode] });
      return;
    }
    const ids = [...regionIDs].sort((left, right) => left - right);
    if (ids.length === 1) {
      scopes.push({ appCodes: [appCode], regionId: ids[0] });
    } else if (ids.length > 1) {
      scopes.push({ appCodes: [appCode], regionIds: ids });
    }
  });
  return scopes;
}

function buildOverviewRequestScopes(appCodes, regions, masterRegions) {
  const selectedApps = new Set((appCodes || []).filter(Boolean));
  if (!regions?.length || regions.includes(ALL)) {
    return [{ appCodes: [...selectedApps] }];
  }
  const configuredByApp = new Map();
  (masterRegions || []).forEach((region) => {
    const appCode = String(region?.app_code || "")
      .trim()
      .toLowerCase();
    const regionID = Number(region?.region_id);
    if (!appCode || !Number.isFinite(regionID) || regionID <= 0) {
      return;
    }
    if (!configuredByApp.has(appCode)) {
      configuredByApp.set(appCode, new Set());
    }
    configuredByApp.get(appCode).add(regionID);
  });

  const byApp = new Map();
  regions.forEach((selection) => {
    const value = String(selection || "").trim();
    if (!value || value === ALL) {
      return;
    }
    if (value.startsWith("app:")) {
      const appCode = value.slice(4).trim().toLowerCase();
      if (appCode) {
        byApp.set(appCode, new Set(configuredByApp.get(appCode) || []));
      }
      return;
    }
    const [rawAppCode, rawRegionID] = value.split(":");
    const appCode = String(rawAppCode || "")
      .trim()
      .toLowerCase();
    const regionID = Number(rawRegionID);
    if (!appCode || !Number.isFinite(regionID) || regionID <= 0) {
      return;
    }
    if (!byApp.has(appCode)) {
      byApp.set(appCode, new Set());
    }
    byApp.get(appCode).add(regionID);
  });

  const scopes = [];
  byApp.forEach((regionIDs, appCode) => {
    if (selectedApps.size && !selectedApps.has(appCode)) {
      return;
    }
    const ids = [...regionIDs].sort((left, right) => left - right);
    if (ids.length === 1) {
      scopes.push({ appCodes: [appCode], regionId: ids[0] });
    } else if (ids.length > 1) {
      scopes.push({ appCodes: [appCode], regionIds: ids });
    } else {
      // 区域目录暂不可用时仍限制到该 App，后端会按权限返回可见范围。
      scopes.push({ appCodes: [appCode] });
    }
  });
  return scopes;
}

function mergeOverviewResponses(results) {
  const merged = { access: results.find((item) => item?.access)?.access || null, apps: [] };
  results.forEach((item) => {
    if (Array.isArray(item?.apps)) {
      merged.apps.push(...item.apps);
    }
  });
  return merged;
}

function mergeRequirementResponses(results) {
  const merged = { access: results.find((item) => item?.access)?.access || null, apps: [] };
  results.forEach((item) => {
    if (Array.isArray(item?.apps)) {
      merged.apps.push(...item.apps);
    }
  });
  return merged;
}
