import { describe, test, expect } from 'vitest';
import { isViral, isSpiking, commentGrowth, seriesGrowth, fillDailyDeltas } from './stats.js';
import type { CommentSnapshot, DailyStat } from './types.js';

const snapshots = (pairs: Array<[string, number]>): CommentSnapshot[] =>
  pairs.map(([date, commentCount]) => ({ date, commentCount }));

describe('isViral (window + views OR comments)', () => {
  const cfg = { windowHours: 24, minViews: 100_000, minComments: 1_000 };
  const UP = '2026-06-29T00:00:00.000Z';
  test('within 24h and views over threshold → viral', () => {
    expect(isViral(UP, 120_000, 10, '2026-06-29T10:00:00.000Z', cfg)).toBe(true);
  });
  test('within 24h and comments over threshold → viral', () => {
    expect(isViral(UP, 500, 1_200, '2026-06-29T10:00:00.000Z', cfg)).toBe(true);
  });
  test('within 24h but neither threshold → not viral', () => {
    expect(isViral(UP, 500, 10, '2026-06-29T10:00:00.000Z', cfg)).toBe(false);
  });
  test('over threshold but older than window → not viral', () => {
    expect(isViral(UP, 200_000, 5_000, '2026-07-01T00:00:00.000Z', cfg)).toBe(false);
  });
  test('null views falls back to 0 for the views check', () => {
    expect(isViral(UP, null, 10, '2026-06-29T10:00:00.000Z', cfg)).toBe(false);
  });
});

describe('isSpiking', () => {
  test('window growth at or above threshold is spiking', () => {
    expect(isSpiking(1000)).toBe(true);
    expect(isSpiking(2500)).toBe(true);
  });

  test('window growth below threshold is not spiking', () => {
    expect(isSpiking(999)).toBe(false);
    expect(isSpiking(0)).toBe(false);
  });

  test('respects a custom threshold', () => {
    expect(isSpiking(300, 300)).toBe(true);
    expect(isSpiking(299, 300)).toBe(false);
  });
});

describe('commentGrowth', () => {
  test('returns growth over the window using the snapshot on the cutoff date', () => {
    // latest 02-08 = 100, snapshot 7 days earlier (02-01) = 30 => +70
    const data = snapshots([
      ['2026-02-01', 30],
      ['2026-02-05', 60],
      ['2026-02-08', 100],
    ]);
    expect(commentGrowth(data, 7)).toBe(70);
  });

  test('1-day growth uses the previous-day snapshot', () => {
    const data = snapshots([
      ['2026-02-07', 90],
      ['2026-02-08', 100],
    ]);
    expect(commentGrowth(data, 1)).toBe(10);
  });

  test('falls back to the earliest snapshot when history is shorter than the window', () => {
    const data = snapshots([
      ['2026-02-07', 90],
      ['2026-02-08', 100],
    ]);
    expect(commentGrowth(data, 7)).toBe(10);
  });

  test('returns 0 for a single snapshot', () => {
    expect(commentGrowth(snapshots([['2026-02-08', 100]]), 7)).toBe(0);
  });

  test('returns 0 for empty input', () => {
    expect(commentGrowth([], 7)).toBe(0);
  });

  test('handles unsorted input', () => {
    const data = snapshots([
      ['2026-02-08', 100],
      ['2026-02-01', 30],
    ]);
    expect(commentGrowth(data, 7)).toBe(70);
  });

  test('asOfDate anchors the window on "now": stale reels score 0', () => {
    const data = snapshots([
      ['2026-02-05', 80],
      ['2026-02-08', 100],
    ]);
    // anchored on the latest snapshot day -> real 3-day growth
    expect(commentGrowth(data, 3, '2026-02-08')).toBe(20);
    // anchored 5 days later -> both baseline and latest are the same old snapshot -> 0
    expect(commentGrowth(data, 3, '2026-02-13')).toBe(0);
  });
});

describe('seriesGrowth (likes/views, nullable)', () => {
  const pts = (xs: Array<[string, number | null]>) => xs.map(([date, value]) => ({ date, value }));

  test('computes growth over the window like commentGrowth', () => {
    expect(seriesGrowth(pts([['2026-02-01', 500], ['2026-02-08', 650]]), 7)).toBe(150);
  });

  test('skips null points (hidden likes / image-post views) without poisoning the delta', () => {
    expect(seriesGrowth(pts([['2026-02-01', 500], ['2026-02-05', null], ['2026-02-08', 800]]), 7)).toBe(300);
  });

  test('returns 0 when all points are null or empty', () => {
    expect(seriesGrowth(pts([['2026-02-01', null], ['2026-02-08', null]]), 7)).toBe(0);
    expect(seriesGrowth([], 7)).toBe(0);
  });
});

describe('fillDailyDeltas', () => {
  test('earliest day has null deltas; later days diff the previous day', () => {
    const input: DailyStat[] = [
      { postId: 1, date: '2026-02-04', commentCount: 0, commentDelta: null, likeCount: null, likeDelta: null, viewCount: null, viewDelta: null, followerCount: 6800, followerDelta: null },
      { postId: 1, date: '2026-02-05', commentCount: 62, commentDelta: null, likeCount: null, likeDelta: null, viewCount: null, viewDelta: null, followerCount: 6835, followerDelta: null },
    ];
    const out = fillDailyDeltas(input);
    expect(out[0]!.commentDelta).toBeNull();
    expect(out[0]!.followerDelta).toBeNull();
    expect(out[1]!.commentDelta).toBe(62);
    expect(out[1]!.followerDelta).toBe(35);
  });

  test('sorts by date ascending before diffing', () => {
    const input: DailyStat[] = [
      { postId: 1, date: '2026-02-05', commentCount: 62, commentDelta: null, likeCount: null, likeDelta: null, viewCount: null, viewDelta: null, followerCount: null, followerDelta: null },
      { postId: 1, date: '2026-02-04', commentCount: 10, commentDelta: null, likeCount: null, likeDelta: null, viewCount: null, viewDelta: null, followerCount: null, followerDelta: null },
    ];
    const out = fillDailyDeltas(input);
    expect(out.map((s) => s.date)).toEqual(['2026-02-04', '2026-02-05']);
    expect(out[1]!.commentDelta).toBe(52);
  });

  test('followerDelta is null when either side lacks a follower count', () => {
    const input: DailyStat[] = [
      { postId: 1, date: '2026-02-04', commentCount: 0, commentDelta: null, likeCount: null, likeDelta: null, viewCount: null, viewDelta: null, followerCount: null, followerDelta: null },
      { postId: 1, date: '2026-02-05', commentCount: 5, commentDelta: null, likeCount: null, likeDelta: null, viewCount: null, viewDelta: null, followerCount: 100, followerDelta: null },
    ];
    const out = fillDailyDeltas(input);
    expect(out[1]!.followerDelta).toBeNull();
  });
});
