import { describe, test, expect, beforeEach } from 'vitest';
import { openDatabase } from '@insta-monitor/db';
import type { Database } from '@insta-monitor/db';
import { DEFAULT_VIRAL_WINDOW_HOURS } from '@insta-monitor/core';
import type { ViralConfig } from '@insta-monitor/core';
import { notifyNewlyViral, baselineNotified, formatAlert, formatDigest } from './notify.js';

const cfg: ViralConfig = { windowHours: DEFAULT_VIRAL_WINDOW_HOURS, minViews: 100_000, minComments: 1_000 };
const NOW = '2026-02-05T12:00:00Z';
const UP = '2026-02-05T00:00:00Z'; // 12h before NOW → inside the 24h window

let db: Database;
beforeEach(() => {
  db = openDatabase(':memory:');
});

/** Seed a reel + its latest snapshot. comments≥1000 or views≥100k (with UP) makes it viral. */
function seed(username: string, shortcode: string, comments: number, views: number | null = null, uploadedAt = UP): number {
  const acct = db.accounts.getByUsername(username)?.id ?? db.accounts.create({ username, autoCollect: true });
  const id = db.posts.upsert({
    accountId: acct, shortcode, type: 'reel',
    url: `https://www.instagram.com/reel/${shortcode}/`, uploadedAt, viewCount: views,
  });
  db.dailyStats.record({ postId: id, date: '2026-02-05', commentCount: comments, viewCount: views });
  return id;
}

describe('listNewlyViral', () => {
  test('returns only viral, unnotified reels', () => {
    seed('comp_a', 'AAA', 1500); // viral (comments)
    seed('comp_b', 'BBB', 200, 150_000); // viral (views)
    seed('comp_c', 'CCC', 50); // not viral
    seed('comp_d', 'DDD', 3000, null, '2026-02-01T00:00:00Z'); // viral count but outside 24h window
    const alerts = db.posts.listNewlyViral(cfg, NOW);
    expect(alerts.map((a) => a.username).sort()).toEqual(['comp_a', 'comp_b']);
  });
});

describe('notifyNewlyViral', () => {
  test('sends one message per reel when at/under the digest threshold, then marks notified', async () => {
    seed('comp_a', 'AAA', 1500);
    seed('comp_b', 'BBB', 2000);
    const sent: string[] = [];
    const n = await notifyNewlyViral(db, cfg, NOW, async (t) => void sent.push(t), { digestOver: 5 });
    expect(n).toBe(2);
    expect(sent).toHaveLength(2);
    // already-notified → a second pass sends nothing
    const again = await notifyNewlyViral(db, cfg, NOW, async (t) => void sent.push(t), { digestOver: 5 });
    expect(again).toBe(0);
    expect(sent).toHaveLength(2);
  });

  test('collapses into a single digest when over the threshold', async () => {
    for (let i = 0; i < 4; i++) seed(`comp_${i}`, `S${i}`, 1500);
    const sent: string[] = [];
    const n = await notifyNewlyViral(db, cfg, NOW, async (t) => void sent.push(t), { digestOver: 3 });
    expect(n).toBe(4);
    expect(sent).toHaveLength(1); // 4 > 3 → one digest
    expect(sent[0]).toContain('떡상 4건');
  });

  test('a send failure leaves unsent reels unnotified (retried next pass)', async () => {
    seed('comp_a', 'AAA', 1500);
    seed('comp_b', 'BBB', 2000);
    let calls = 0;
    const n = await notifyNewlyViral(
      db, cfg, NOW,
      async () => { if (++calls === 2) throw new Error('telegram down'); },
      { digestOver: 5 },
    );
    expect(n).toBe(1); // only the first succeeded
    // the failed one is still pending → next pass picks it up
    const remaining = db.posts.listNewlyViral(cfg, NOW);
    expect(remaining).toHaveLength(1);
  });
});

describe('baselineNotified', () => {
  test('marks current viral reels notified without sending (first-activation flood guard)', () => {
    seed('comp_a', 'AAA', 1500);
    seed('comp_b', 'BBB', 50); // not viral → stays alertable
    const marked = baselineNotified(db, cfg, NOW);
    expect(marked).toBe(1);
    expect(db.posts.listNewlyViral(cfg, NOW)).toHaveLength(0);
    // a reel that becomes viral LATER still alerts
    seed('comp_b', 'BBB', 1500);
    expect(db.posts.listNewlyViral(cfg, NOW).map((a) => a.username)).toEqual(['comp_b']);
  });
});

describe('formatting', () => {
  test('individual message has account, stats, link', () => {
    const msg = formatAlert({ id: 1, username: 'comp_a', url: 'https://x/reel/AAA/', viewCount: 123_400, commentCount: 1_520 });
    expect(msg).toContain('@comp_a');
    expect(msg).toContain('123,400');
    expect(msg).toContain('1,520');
    expect(msg).toContain('https://x/reel/AAA/');
  });
  test('digest lists each reel with a count header', () => {
    const msg = formatDigest([
      { id: 1, username: 'comp_a', url: 'u1', viewCount: 120_000, commentCount: 10 },
      { id: 2, username: 'comp_b', url: 'u2', viewCount: null, commentCount: 1_650 },
    ]);
    expect(msg).toContain('떡상 2건');
    expect(msg).toContain('@comp_a');
    expect(msg).toContain('@comp_b');
  });
});
