// FILE: _St/IG_StYellow/test_ext_stats_incremental.js | ROLE: IG_StYellow 회귀 테스트
const fs = require('fs');
const path = require('path');
const assert = require('assert');
const vm = require('vm');

(async () => {
  const root = __dirname;
  const side = fs.readFileSync(path.join(root, 'side.js'), 'utf8');
  const manifest = JSON.parse(fs.readFileSync(path.join(root, 'manifest.json'), 'utf8'));
  assert.strictEqual(manifest.version, '2.3.8.2880');

  const start = side.indexOf('const TE_STATS_MODE_KEY');
  const end = side.indexOf('function teArmWsAutoEnter', start);
  assert.ok(start >= 0 && end > start, 'statistics block missing');

  const store = {'stats/mode': 'linked'};
  const wsCalls = [];
  const ctx = {
    Promise, Map, Date, Number, Math, String, Array, Error, JSON, URL, encodeURIComponent,
    document: {getElementById: () => null, createElement: () => ({style:{}, appendChild(){}, classList:{toggle(){}}})},
    teLocalGet: (key, fallback) => Object.prototype.hasOwnProperty.call(store, key) ? store[key] : fallback,
    teLocalSet: (key, value) => { store[key] = value; },
    teTopMetricField: () => 'play_count',
    teServerSortField: (field) => field,
    G: ['like_count','comment_count','media_repost_count','created_at','play_count','','engagement_rate','share_count'], Z: 4,
    teOwnerAccountForMedia: (media) => String(media.username || ''),
    teMediaPageUrl: (media) => `https://www.instagram.com/p/${media.code}/`,
    teParticipationScore: (media) => Number(media.like_count || 0) + Number(media.comment_count || 0) * 3 + Number(media.media_repost_count || 0) * 4,
    teCurrentTargetUrl: () => 'https://www.instagram.com/alpha/',
    fetch: async (_url, req) => ({ok:true, json:async()=>({ok:true,saved_count:JSON.parse(req.body).items.length})}),
    teAiwkWsRequest: async (act, payload) => {
      assert.strictEqual(act, 'ig_observation_sync_hour');
      wsCalls.push(payload.items.map((item) => item.shortcode));
      return {ok:true, sent_count:payload.items.length, items:[]};
    },
    dbg: () => {}
  };
  vm.createContext(ctx);
  vm.runInContext(`${side.slice(start, end)}; this.sync=teStatsSyncAccountHour; this.chunk=teStatsChunk; this.state=teStatsHourState; this.localChanged=teStatsChangedLocalItems; this.tenMinute=teStatsTenMinuteBucket;`, ctx);

  const make = (i, views = i) => ({
    username:'alpha', shortcode:`S${String(i).padStart(3,'0')}`, views_available:1,
    views, likes:i, comments:i % 7, reposts:i % 3,
    sort_by:'play_count', sort_value:views
  });
  const items = Array.from({length:250}, (_, i) => make(i + 1));

  const localBase = [{...make(1), observed_at:'2026-07-27T03:21:00Z'}];
  assert.strictEqual(ctx.tenMinute(localBase[0].observed_at), '2026-07-27T03:20');
  assert.strictEqual(ctx.localChanged(localBase, 'https://www.instagram.com/alpha/').length, 1);
  assert.strictEqual(ctx.localChanged([{...localBase[0], observed_at:'2026-07-27T03:29:59Z'}], 'https://www.instagram.com/alpha/').length, 0);
  // 같은 10분 구간 안의 변화는 다음 10분 확인 시점까지 저장하지 않는다.
  assert.strictEqual(ctx.localChanged([{...localBase[0], views:2, sort_value:2, observed_at:'2026-07-27T03:29:59Z'}], 'https://www.instagram.com/alpha/').length, 0);
  // 새 10분 구간에서 실제 통계가 변경됐을 때만 저장 대상으로 나온다.
  assert.strictEqual(ctx.localChanged([{...localBase[0], views:2, sort_value:2, observed_at:'2026-07-27T03:30:00Z'}], 'https://www.instagram.com/alpha/').length, 1);
  // 다음 10분 구간에서 값이 그대로면 저장하지 않는다.
  assert.strictEqual(ctx.localChanged([{...localBase[0], views:2, sort_value:2, observed_at:'2026-07-27T03:40:00Z'}], 'https://www.instagram.com/alpha/').length, 0);

  const first = await ctx.sync('alpha', items, '2026-07-27T03:20:00Z');
  assert.deepStrictEqual(wsCalls.map((rows) => rows.length), [100, 100, 50]);
  assert.strictEqual(first.sent_count, 250);

  wsCalls.length = 0;
  const second = await ctx.sync('alpha', items, '2026-07-27T03:40:00Z');
  assert.strictEqual(second.skipped, 'same_hour_no_change');
  assert.strictEqual(wsCalls.length, 0);

  wsCalls.length = 0;
  const changed = items.map((item) => ({...item}));
  changed[149].views += 1;
  changed[149].sort_value = changed[149].views;
  const third = await ctx.sync('alpha', changed, '2026-07-27T03:50:00Z');
  assert.deepStrictEqual(wsCalls.map((rows) => rows.length), [1]);
  assert.strictEqual(third.sent_count, 1);

  console.log('OK test_ext_stats_incremental v272');
})().catch((error) => {
  console.error(error);
  process.exit(1);
});
