/**
 * 视频号：作品维度「纯浏览器自动化」→ 导入 work_day_statistics
 *
 * 流程：Node 端 Playwright 完成（不依赖 Python）：
 * 1. 打开 statistic/post → 点击单篇视频 → 点击近30天
 * 2. 监听 post_list 获取 exportId->objectId，可选更新 works 表 yesterday_*
 * 3. 遍历列表，按 exportId 匹配 DB 作品，匹配则点击查看 → 详情页监听 feed_aggreagate_data_by_tab_type
 * 4. 解析「全部」tab 的 browse/like/comment/forward/fav/follow，写入 work_day_statistics
 */

import { chromium, type Browser } from 'playwright';
import { AppDataSource, PlatformAccount, Work } from '../models/index.js';
import { BrowserManager } from '../automation/browser.js';
import { logger } from '../utils/logger.js';
import { CookieManager } from '../automation/cookie.js';
import { WorkDayStatisticsService } from './WorkDayStatisticsService.js';
import type { ProxyConfig } from '@media-manager/shared';

type PlaywrightCookie = {
  name: string;
  value: string;
  domain?: string;
  path?: string;
  url?: string;
  expires?: number;
  httpOnly?: boolean;
  secure?: boolean;
  sameSite?: 'Lax' | 'None' | 'Strict';
};

function tryDecryptCookieData(cookieData: string | null): string | null {
  if (!cookieData) return null;
  const raw = cookieData.trim();
  if (!raw) return null;
  try {
    return CookieManager.decrypt(raw);
  } catch {
    return raw;
  }
}

function parseCookiesFromAccount(cookieData: string | null): PlaywrightCookie[] {
  const raw = tryDecryptCookieData(cookieData);
  if (!raw || !raw.trim()) return [];

  const s = raw.trim();
  if (s.startsWith('[') || s.startsWith('{')) {
    try {
      const parsed = JSON.parse(s);
      const arr = Array.isArray(parsed) ? parsed : (parsed?.cookies ? parsed.cookies : []);
      if (!Array.isArray(arr)) return [];
      return arr
        .map((c: any) => {
          const name = String(c?.name ?? '').trim();
          const value = String(c?.value ?? '').trim();
          if (!name) return null;
          const domain = c?.domain ? String(c.domain) : undefined;
          const pathVal = c?.path ? String(c.path) : '/';
          const url = !domain ? 'https://channels.weixin.qq.com' : undefined;
          const sameSiteRaw = c?.sameSite;
          const sameSite =
            sameSiteRaw === 'Lax' || sameSiteRaw === 'None' || sameSiteRaw === 'Strict'
              ? sameSiteRaw
              : undefined;
          return {
            name,
            value,
            domain,
            path: pathVal,
            url,
            expires: typeof c?.expires === 'number' ? c.expires : undefined,
            httpOnly: typeof c?.httpOnly === 'boolean' ? c.httpOnly : undefined,
            secure: typeof c?.secure === 'boolean' ? c.secure : undefined,
            sameSite,
          } satisfies PlaywrightCookie;
        })
        .filter(Boolean) as PlaywrightCookie[];
    } catch {
      // fallthrough
    }
  }

  const pairs = s.split(';').map((p) => p.trim()).filter(Boolean);
  const cookies: PlaywrightCookie[] = [];
  for (const p of pairs) {
    const idx = p.indexOf('=');
    if (idx <= 0) continue;
    const name = p.slice(0, idx).trim();
    const value = p.slice(idx + 1).trim();
    if (!name) continue;
    cookies.push({ name, value, url: 'https://channels.weixin.qq.com' });
  }
  return cookies;
}

async function createBrowserForAccount(proxy: ProxyConfig | null, headless: boolean): Promise<{
  browser: Browser;
  shouldClose: boolean;
}> {
  if (proxy?.enabled) {
    const server = `${proxy.type}://${proxy.host}:${proxy.port}`;
    const browser = await chromium.launch({
      headless,
      proxy: {
        server,
        username: proxy.username,
        password: proxy.password,
      },
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-gpu',
        '--window-size=1920,1080',
      ],
    });
    return { browser, shouldClose: true };
  }
  const browser = await BrowserManager.getBrowser({ headless });
  return { browser, shouldClose: false };
}

function parseChineseNumberLike(input: unknown): number | null {
  if (input === null || input === undefined) return null;
  const s = String(input).trim();
  if (!s) return null;
  const plain = s.replace(/,/g, '');
  const wan = plain.match(/^(\d+(\.\d+)?)\s*万$/);
  if (wan) return Math.round(Number(wan[1]) * 10000);
  const yi = plain.match(/^(\d+(\.\d+)?)\s*亿$/);
  if (yi) return Math.round(Number(yi[1]) * 100000000);
  const n = Number(plain.replace(/[^\d.-]/g, ''));
  if (Number.isFinite(n)) return Math.round(n);
  return null;
}

/** 从昨天往前推 N 天的日期数组（index 0 = 最早），与接口数组顺序一致 */
function getRecentChinaDates(days: number): Date[] {
  const formatter = new Intl.DateTimeFormat('en-CA', {
    timeZone: 'Asia/Shanghai',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  });
  const parts = formatter.formatToParts(new Date());
  const get = (type: string) => parts.find((p) => p.type === type)?.value ?? '0';
  const y = parseInt(get('year'), 10);
  const m = parseInt(get('month'), 10) - 1;
  const d = parseInt(get('day'), 10);
  const today = new Date(y, m, d, 0, 0, 0, 0);
  const end = new Date(today);
  end.setDate(end.getDate() - 1);
  const start = new Date(end);
  start.setDate(end.getDate() - (days - 1));
  const dates: Date[] = [];
  for (let i = 0; i < days; i++) {
    const dt = new Date(start);
    dt.setDate(start.getDate() + i);
    dt.setHours(0, 0, 0, 0);
    dates.push(dt);
  }
  return dates;
}

/** 四舍五入保留两位小数，用于 yesterday_completion_rate / yesterday_avg_watch_duration 入库 */
function round2(value: number | null | undefined): number {
  if (value == null || !Number.isFinite(value)) return 0;
  return Math.round(value * 100) / 100;
}

const STATISTIC_POST_URL = 'https://channels.weixin.qq.com/platform/statistic/post';
const TAB_SINGLE_VIDEO_SELECTORS = [
  // 与当前后台 DOM 一致：ul.weui-desktop-tab_navs_inner 下 li.weui-desktop-tab_nav 内的 a
  'ul.weui-desktop-tab_navs_inner li.weui-desktop-tab_nav a:has-text("单篇视频")',
  'li.weui-desktop-tab_nav a:has-text("单篇视频")',
  'div.weui-desktop-tab__navs ul li:nth-child(2) a',
  'a:has-text("单篇视频")',
];
const NEAR_30_DAYS_SELECTORS = [
  'div.post-single-wrap div.weui-desktop-radio-group.radio-group label:has-text("近30天")',
  'div.post-single-wrap div.filter-wrap div.weui-desktop-radio-group label:nth-child(2)',
  'div.post-single-wrap label:has-text("近30天")',
  'div.weui-desktop-radio-group label:has-text("近30天")',
  'label:has-text("近30天")',
];
const VIEW_BTN_SELECTORS = (objectId: string) => [
  // 与当前后台 DOM 一致：table.ant-table-fixed 下 tr[data-row-key] 内 a.detail-wrap
  `table.ant-table-fixed tr[data-row-key="${objectId}"] a.detail-wrap`,
  `div.ant-table-fixed tr[data-row-key="${objectId}"] a.detail-wrap`,
  `tr[data-row-key="${objectId}"] div.slot-wrap a.detail-wrap`,
  `tr[data-row-key="${objectId}"] a.detail-wrap`,
  `div.ant-table-fixed-right tr[data-row-key="${objectId}"] a.detail-wrap`,
  `tr[data-row-key="${objectId}"] a:has-text("查看")`,
  `tr[data-row-key="${objectId}"] a`,
];

export class WeixinVideoWorkStatisticsImportService {
  private accountRepository = AppDataSource.getRepository(PlatformAccount);
  private workRepository = AppDataSource.getRepository(Work);
  private workDayStatisticsService = new WorkDayStatisticsService();

  static async runDailyImport(): Promise<void> {
    const svc = new WeixinVideoWorkStatisticsImportService();
    await svc.runDailyImportForAllWeixinVideoAccounts();
  }

  /** 仅同步指定账号（用于测试/补数），showBrowser=true 时显示浏览器窗口；仅同步 workIds 时用于作品补数 */
  static async runDailyImportForAccount(
    accountId: number,
    showBrowser = false,
    onlyWorkIds?: number[]
  ): Promise<void> {
    const svc = new WeixinVideoWorkStatisticsImportService();
    const account = await svc.accountRepository.findOne({
      where: { id: accountId, platform: 'weixin_video' as any },
    });
    if (!account) {
      throw new Error(`未找到视频号账号 id=${accountId}`);
    }
    logger.info(
      `[WX WorkStats] 单账号同步 accountId=${accountId} showBrowser=${showBrowser}` +
        (onlyWorkIds?.length ? ` 仅作品 ${onlyWorkIds.length} 个` : '')
    );
    await svc.importAccountWorksStatistics(account, showBrowser, onlyWorkIds?.length ? { onlyWorkIds } : undefined);
  }

  /** 仅同步指定作品（用于接口/测试），showBrowser=true 时显示浏览器窗口；返回 inserted/updated */
  static async runDailyImportForWork(
    workId: number,
    showBrowser = false
  ): Promise<{ inserted: number; updated: number }> {
    const svc = new WeixinVideoWorkStatisticsImportService();
    const work = await svc.workRepository.findOne({
      where: { id: workId, platform: 'weixin_video' as any },
      relations: ['account'],
    });
    if (!work) throw new Error(`未找到视频号作品 id=${workId}`);
    const account = work.account as PlatformAccount;
    if (!account) throw new Error(`作品 ${workId} 无关联账号`);
    logger.info(`[WX WorkStats] 单作品同步 workId=${workId} accountId=${account.id} showBrowser=${showBrowser}`);
    return svc.importAccountWorksStatistics(account, showBrowser, { onlyWorkIds: [workId] });
  }

  async runDailyImportForAllWeixinVideoAccounts(): Promise<void> {
    const accounts = await this.accountRepository.find({
      where: { platform: 'weixin_video' as any },
    });
    logger.info(`[WX WorkStats] Start import for ${accounts.length} weixin_video accounts`);
    for (const account of accounts) {
      try {
        await this.importAccountWorksStatistics(account);
      } catch (e) {
        logger.error(
          `[WX WorkStats] Account failed. accountId=${account.id} name=${account.accountName || ''}`,
          e
        );
      }
    }
    logger.info('[WX WorkStats] All accounts done');
  }

  private async importAccountWorksStatistics(
    account: PlatformAccount,
    showBrowser = false,
    options?: { onlyWorkIds?: number[] }
  ): Promise<{ inserted: number; updated: number }> {
    const cookies = parseCookiesFromAccount(account.cookieData);
    if (!cookies.length) {
      logger.warn(`[WX WorkStats] accountId=${account.id} cookieData 为空或无法解析，跳过`);
      return { inserted: 0, updated: 0 };
    }

    let works = await this.workRepository.find({
      where: { accountId: account.id, platform: 'weixin_video' as any },
    });
    if (options?.onlyWorkIds?.length) {
      const set = new Set(options.onlyWorkIds);
      works = works.filter((w) => set.has(w.id));
    }
    if (!works.length) {
      logger.info(`[WX WorkStats] accountId=${account.id} 没有作品，跳过`);
      return { inserted: 0, updated: 0 };
    }

    const exportIdToWorkId = new Map<string, number>();
    for (const w of works) {
      const pvid = (w.platformVideoId ?? '').trim();
      if (!pvid) continue;
      exportIdToWorkId.set(pvid, w.id);
      if (pvid.includes('/')) exportIdToWorkId.set(pvid.split('/').pop()!, w.id);
    }
    if (exportIdToWorkId.size === 0) {
      logger.info(`[WX WorkStats] accountId=${account.id} 无有效 platform_video_id，跳过`);
      return { inserted: 0, updated: 0 };
    }

    const headless =
      process.env.WX_IMPORT_HEADLESS === '0' ? false : !showBrowser;
    const { browser, shouldClose } = await createBrowserForAccount(
      account.proxyConfig as ProxyConfig | null,
      headless
    );

    let totalProcessed = 0;
    let totalSkipped = 0;
    let inserted = 0;
    let updated = 0;
    let worksUpdated = 0;

    try {
      const context = await browser.newContext({
        acceptDownloads: true,
        viewport: { width: 1920, height: 1080 },
        locale: 'zh-CN',
        timezoneId: 'Asia/Shanghai',
        userAgent:
          'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      });
      context.setDefaultTimeout(60_000);
      await context.addCookies(cookies as any);

      const page = await context.newPage();

      const postListData = { list: [] as Array<{ exportId?: string; objectId?: string; [k: string]: any }> };

      // 只采纳 _pageUrl 为该值的 post_list 响应（统计-作品页）。请求里可能是编码或解码形式，统一解码后比较
      const POST_LIST_PAGE_URL_DECODED = 'https://channels.weixin.qq.com/micro/statistic/post';
      const normalizePageUrl = (raw: string): string => {
        if (!raw || !raw.trim()) return '';
        try {
          return decodeURIComponent(raw.trim());
        } catch {
          return raw.trim();
        }
      };
      const getPageUrl = (req: { url: () => string; postData: () => string | undefined }): string => {
        try {
          const fromUrl = new URL(req.url()).searchParams.get('_pageUrl') ?? '';
          if (fromUrl) return normalizePageUrl(fromUrl);
          const postData = req.postData();
          if (typeof postData === 'string') {
            const p = JSON.parse(postData);
            const raw = (p?._pageUrl ?? p?._page_url ?? '') as string;
            return normalizePageUrl(raw);
          }
        } catch {
          // ignore
        }
        return '';
      };
      page.on('response', async (response) => {
        try {
          const url = response.request().url();
          if (!url.includes('statistic/post_list')) return;
          const req = response.request();
          const pageUrl = getPageUrl(req);
          const reqUrl = new URL(url);
          logger.info(
            `[WX WorkStats] post_list 请求: _pageUrl=${reqUrl.searchParams.get('_pageUrl') ?? '(query无)'} getPageUrl=${pageUrl || '(空)'}`
          );
          const postData = req.postData();
          if (typeof postData === 'string') {
            try {
              const payload = JSON.parse(postData);
              const { _aid, _rid, _pageUrl: _, ...rest } = payload;
              logger.info(`[WX WorkStats] post_list 请求体(不含 aid/rid): ${JSON.stringify(rest)}`);
            } catch {
              logger.info(`[WX WorkStats] post_list 请求体(原始): ${postData.slice(0, 500)}`);
            }
          }
          if (pageUrl !== POST_LIST_PAGE_URL_DECODED) return;
          const body = await response.json().catch(() => ({}));
          logger.info(
            `[WX WorkStats] post_list 返回: errCode=${body?.errCode} errMsg=${body?.errMsg} totalCount=${body?.data?.totalCount} listLength=${body?.data?.list?.length ?? 0}`
          );
          logger.info(`[WX WorkStats] post_list 返回完整 body: ${JSON.stringify(body, null, 2)}`);
          if (body?.errCode === 0 && body?.data?.list) {
            postListData.list = body.data.list;
          }
        } catch {
          // ignore
        }
      });

      await page.goto(STATISTIC_POST_URL, { waitUntil: 'domcontentloaded', timeout: 30_000 });
      await page.waitForTimeout(showBrowser ? 5000 : 3000);

      if (page.url().includes('login') || page.url().includes('passport')) {
        throw new Error('Cookie 已过期，请重新登录');
      }

      let singleVideoClicked = false;
      for (const sel of TAB_SINGLE_VIDEO_SELECTORS) {
        const baseLoc = page.locator(sel);
        if ((await baseLoc.count()) > 0) {
          try {
            await baseLoc.nth(0).waitFor({ state: 'visible', timeout: 3000 });
            await baseLoc.nth(0).click({ timeout: 3000 });
            singleVideoClicked = true;
            logger.info(`[WX WorkStats] accountId=${account.id} 已点击「单篇视频」tab, selector=${sel}`);
            break;
          } catch (e) {
            logger.warn(`[WX WorkStats] accountId=${account.id} 点击「单篇视频」失败, selector=${sel}`, e);
          }
        }
      }
      if (!singleVideoClicked) {
        logger.warn(`[WX WorkStats] accountId=${account.id} 未找到或未点击成功「单篇视频」tab，可能影响 post_list 数据`);
      }
      await page.waitForTimeout(2000);

      postListData.list = [];
      let near30Clicked = false;
      for (const sel of NEAR_30_DAYS_SELECTORS) {
        const baseLoc = page.locator(sel);
        if ((await baseLoc.count()) > 0) {
          try {
            await baseLoc.nth(0).waitFor({ state: 'visible', timeout: 3000 });
            await baseLoc.nth(0).click({ timeout: 3000 });
            near30Clicked = true;
            logger.info(`[WX WorkStats] accountId=${account.id} 已点击「近30天」, selector=${sel}`);
            break;
          } catch (e) {
            logger.warn(`[WX WorkStats] accountId=${account.id} 点击「近30天」失败, selector=${sel}`, e);
          }
        }
      }
      if (!near30Clicked) {
        logger.warn(`[WX WorkStats] accountId=${account.id} 未找到或未点击成功「近30天」，post_list 可能无数据`);
      }
      await page.waitForTimeout(5000);

      const items = postListData.list;
      if (!items.length) {
        logger.warn(`[WX WorkStats] accountId=${account.id} 未监听到 post_list 或列表为空`);
        return { inserted: 0, updated: 0 };
      }

      // 首次：用 post_list 更新 works 表 yesterday_*
      const workUpdates: Array<{
        work_id: number;
        yesterday_play_count: number;
        yesterday_like_count: number;
        yesterday_recommend_count: number;
        yesterday_comment_count: number;
        yesterday_share_count: number;
        yesterday_follow_count: number;
        yesterday_completion_rate: string;
        yesterday_avg_watch_duration: string;
      }> = [];
      for (const it of items) {
        const eid = (it.exportId ?? '').trim();
        if (!eid) continue;
        let workId = exportIdToWorkId.get(eid);
        if (workId == null) {
          for (const [k, v] of exportIdToWorkId) {
            if (eid.includes(k) || k.includes(eid)) {
              workId = v;
              break;
            }
          }
        }
        if (workId == null) continue;
        const readCount = Number(it.readCount) || 0;
        const recommendCount = Number(it.likeCount) || 0;
        const likeCount = Number(it.favCount) || 0;
        const commentCount = Number(it.commentCount) || 0;
        const forwardCount = Number(it.forwardCount) || 0;
        const followCount = Number(it.followCount) || 0;
        const fullPlayRate = it.fullPlayRate;
        const compRate = fullPlayRate != null ? `${round2(Number(fullPlayRate) * 100)}%` : '0';
        const avgSec = it.avgPlayTimeSec;
        const avgDur = avgSec != null ? `${round2(Number(avgSec))}秒` : '0';
        workUpdates.push({
          work_id: workId,
          yesterday_play_count: readCount,
          yesterday_like_count: likeCount,
          yesterday_recommend_count: recommendCount,
          yesterday_comment_count: commentCount,
          yesterday_share_count: forwardCount,
          yesterday_follow_count: followCount,
          yesterday_completion_rate: compRate,
          yesterday_avg_watch_duration: avgDur,
        });
      }
      if (workUpdates.length > 0) {
        for (const u of workUpdates) {
          const result = await this.workRepository.update(u.work_id, {
            yesterdayPlayCount: u.yesterday_play_count,
            yesterdayLikeCount: u.yesterday_like_count,
            yesterdayRecommendCount: u.yesterday_recommend_count,
            yesterdayCommentCount: u.yesterday_comment_count,
            yesterdayShareCount: u.yesterday_share_count,
            yesterdayFollowCount: u.yesterday_follow_count,
            yesterdayCompletionRate: u.yesterday_completion_rate,
            yesterdayAvgWatchDuration: u.yesterday_avg_watch_duration,
          });
          if (result.affected && result.affected > 0) worksUpdated += result.affected;
        }
      }

      const processedExportIds = new Set<string>();

      const ensureSingleVideoNear30 = async () => {
        for (const sel of TAB_SINGLE_VIDEO_SELECTORS) {
          const baseLoc = page.locator(sel);
          if ((await baseLoc.count()) > 0) {
            await baseLoc.nth(0).waitFor({ state: 'visible', timeout: 3000 }).catch(() => undefined);
            await baseLoc.nth(0).click({ timeout: 3000 }).catch(() => undefined);
            break;
          }
        }
        await page.waitForTimeout(2000);
        for (const sel of NEAR_30_DAYS_SELECTORS) {
          const baseLoc = page.locator(sel);
          if ((await baseLoc.count()) > 0) {
            await baseLoc.nth(0).waitFor({ state: 'visible', timeout: 3000 }).catch(() => undefined);
            await baseLoc.nth(0).click({ timeout: 3000 }).catch(() => undefined);
            break;
          }
        }
        await page.waitForTimeout(3000);
      };

      for (let idx = 0; idx < items.length; idx++) {
        const item = items[idx]!;
        const eid = (item.exportId ?? item.export_id ?? '').trim();
        const oid = (item.objectId ?? item.object_id ?? '').trim();
        if (!oid) continue;
        if (processedExportIds.has(eid)) continue;

        if (idx > 0) {
          await ensureSingleVideoNear30();
        }

        let workId = exportIdToWorkId.get(eid);
        if (workId == null) {
          for (const [k, v] of exportIdToWorkId) {
            if (eid.includes(k) || k.includes(eid)) {
              workId = v;
              break;
            }
          }
        }
        if (workId == null) {
          totalSkipped += 1;
          continue;
        }

        // 先挂好监听再点击「查看」，避免响应在 waitForResponse 之前就返回导致漏接
        const FEED_AGGREAGATE_PAGE_URL = 'https://channels.weixin.qq.com/micro/statistic/postDetail';
        const getReqPageUrl = (req: { url: () => string; postData: () => string | undefined }): string => {
          try {
            let pageUrl = new URL(req.url()).searchParams.get('_pageUrl') ?? '';
            if (pageUrl) return decodeURIComponent(pageUrl);
            const postData = req.postData();
            if (typeof postData === 'string') {
              const p = JSON.parse(postData);
              const raw = (p?._pageUrl ?? p?._page_url ?? '') as string;
              return raw ? decodeURIComponent(raw) : '';
            }
          } catch {
            // ignore
          }
          return '';
        };
        const responsePromise = page.waitForResponse(
          (r) => {
            if (!r.url().includes('feed_aggreagate_data_by_tab_type')) return false;
            const pageUrl = getReqPageUrl(r.request());
            if (pageUrl !== FEED_AGGREAGATE_PAGE_URL) {
              logger.info(`[WX WorkStats] feed_aggreagate 忽略 _pageUrl=${pageUrl}`);
              return false;
            }
            return true;
          },
          { timeout: 15_000 }
        );

        let viewClicked = false;
        for (const sel of VIEW_BTN_SELECTORS(oid)) {
          const viewBtn = page.locator(sel);
          if ((await viewBtn.count()) > 0) {
            try {
              await viewBtn.nth(0).waitFor({ state: 'visible', timeout: 3000 });
              logger.info(`[WX WorkStats] accountId=${account.id} 点击「查看」oid=${oid} selector=${sel}`);
              await viewBtn.nth(0).click();
              viewClicked = true;
              break;
            } catch (e) {
              logger.warn(`[WX WorkStats] 点击「查看」失败 oid=${oid} selector=${sel}`, e);
            }
          }
        }
        if (!viewClicked) {
          totalSkipped += 1;
          continue;
        }

        let body: { data?: { dataByFanstype?: Array<{ dataByTabtype?: Array<{ tabTypeName?: string; tabType?: number; data?: any }> }>; feedData?: Array<{ totalData?: any }> } } | null = null;
        try {
          const res = await responsePromise;
          const json = await res.json().catch(() => null);
          if (json?.errCode === 0 && json?.data) {
            body = json;
            logger.info(`[WX WorkStats] accountId=${account.id} 收到 feed_aggreagate 详情数据 oid=${oid}`);
          }
        } catch (e) {
          logger.warn(`[WX WorkStats] accountId=${account.id} 等待 feed_aggreagate(postDetail) 超时或失败 oid=${oid}`, e);
        }

        if (!body?.data) {
          await page.goBack().catch(() => undefined);
          await page.waitForTimeout(2000);
          continue;
        }

        let tabAll: Record<string, unknown[] | undefined> | undefined;
        for (const fanItem of body.data.dataByFanstype ?? []) {
          for (const tabItem of fanItem.dataByTabtype ?? []) {
            if (tabItem.tabTypeName === '全部' || tabItem.tabType === 999) {
              tabAll = tabItem.data as Record<string, unknown[] | undefined>;
              break;
            }
          }
          if (tabAll) break;
        }
        if (!tabAll) {
          const feedData = body.data.feedData;
          tabAll = feedData?.[0]?.totalData as Record<string, unknown[] | undefined> | undefined;
        }
        if (!tabAll) {
          await page.goBack().catch(() => undefined);
          await page.waitForTimeout(2000);
          continue;
        }

        const browse = Array.isArray(tabAll.browse) ? tabAll.browse : [];
        const n = browse.length;
        if (n === 0) {
          await page.goBack().catch(() => undefined);
          await page.waitForTimeout(2000);
          continue;
        }

        const likeArr = Array.isArray(tabAll.like) ? tabAll.like : [];
        const commentArr = Array.isArray(tabAll.comment) ? tabAll.comment : [];
        const forwardArr = Array.isArray(tabAll.forward) ? tabAll.forward : [];
        const favArr = Array.isArray(tabAll.fav) ? tabAll.fav : [];
        const followArr = Array.isArray(tabAll.follow) ? tabAll.follow : [];
        const dates = getRecentChinaDates(n);

        const batchItems: Array<{
          workId: number;
          recordDate: Date;
          playCount: number;
          likeCount: number;
          recommendCount: number;
          commentCount: number;
          shareCount: number;
          collectCount: number;
          followCount: number;
          completionRate: string;
          avgWatchDuration: string;
        }> = [];
        for (let i = 0; i < n; i++) {
          const recDate = dates[i]!;
          const play = parseChineseNumberLike(browse[i]) ?? 0;
          const recommend = parseChineseNumberLike(likeArr[i]) ?? 0;
          const like = parseChineseNumberLike(favArr[i]) ?? 0;
          const comment = parseChineseNumberLike(commentArr[i]) ?? 0;
          const share = parseChineseNumberLike(forwardArr[i]) ?? 0;
          const follow = parseChineseNumberLike(followArr[i]) ?? 0;
          batchItems.push({
            workId,
            recordDate: recDate,
            playCount: play,
            likeCount: like,
            recommendCount: recommend,
            commentCount: comment,
            shareCount: share,
            collectCount: 0,
            followCount: follow,
            completionRate: '0',
            avgWatchDuration: '0',
          });
        }

        const result = await this.workDayStatisticsService.saveStatisticsForDateBatch(batchItems);
        inserted += result.inserted;
        updated += result.updated;
        totalProcessed += 1;
        processedExportIds.add(eid);

        await page.goBack().catch(() => undefined);
        await page.waitForTimeout(2000);
      }

      logger.info(
        `[WX WorkStats] accountId=${account.id} 完成: 处理 ${totalProcessed} 个, 跳过 ${totalSkipped} 个, 新增 ${inserted} 条, 更新 ${updated} 条` +
          (worksUpdated > 0 ? `, works 表更新 ${worksUpdated} 条` : '')
      );
      if (shouldClose) await browser.close().catch(() => undefined);
      return { inserted, updated };
    } catch (e) {
      if (shouldClose) await browser.close().catch(() => undefined);
      throw e;
    }
  }
}
