/**
 * test/alertDedup.test.js
 *
 * The dedup logic in services/alertBus.js used a single 30-min window
 * for every alert category. That meant persistent-state alerts
 * (auto-escalate blocked, health-blocked, api_error) spammed the
 * operator every 30-60 min. Per-category windows fix this:
 *   - auto_escalate_blocked → 7d  (permanent property of the takedown)
 *   - health_blocked        → 24h
 *   - api_error             → 6h
 *   - default               → 30 min
 *
 * These tests verify the bucketing logic and the default fallback.
 */

'use strict';

const assert = require('assert');
const { setupFreshDb, teardown } = require('./_setup');

// Stub out email so raise() doesn't try to actually send
const emailStub = require('./_stripeMock');  // any stub — we don't use Stripe here
// We just need to load the alertBus AFTER the DB is initialised.
process.env.RESEND_ALERT_TO = '';

const alertBus = require('../services/alertBus');

const SEVEN_DAYS  = 7 * 24 * 60 * 60 * 1000;
const SIX_HOURS   = 6 * 60 * 60 * 1000;
const TWENTY_FOUR = 24 * 60 * 60 * 1000;
const THIRTY_MIN  = 30 * 60 * 1000;

const tests = [
  {
    name: 'auto_escalate_blocked dedup window = 7 days (persistent state)',
    fn: () => {
      setupFreshDb();
      try {
        const r1 = alertBus.raise({
          severity: 'warn', category: 'escalation',
          title: 'Auto-escalate blocked: example.com → registrar step is a URL',
          dedupe_key: 'auto_escalate_blocked:42:1',
        });
        assert.ok(r1.alert, 'first raise returns an alert');
        assert.strictEqual(r1.deduped, false, 'first is not deduped');
        // Immediately try again with the same key — should be deduped.
        const r2 = alertBus.raise({
          severity: 'warn', category: 'escalation',
          title: 'Auto-escalate blocked: example.com → registrar step is a URL',
          dedupe_key: 'auto_escalate_blocked:42:1',
        });
        assert.strictEqual(r2.deduped, true, 'immediate re-raise with same key dedupes');
      } finally { teardown(); }
    },
  },
  {
    name: 'health_blocked dedup window = 24h (persistent probe failure)',
    fn: () => {
      setupFreshDb();
      try {
        const r1 = alertBus.raise({
          severity: 'info', category: 'takedown',
          title: 'Health probe blocked: example.com',
          dedupe_key: 'health_blocked:22',
        });
        assert.strictEqual(r1.deduped, false);
        const r2 = alertBus.raise({
          severity: 'info', category: 'takedown',
          title: 'Health probe blocked: example.com',
          dedupe_key: 'health_blocked:22',
        });
        assert.strictEqual(r2.deduped, true, 're-raise dedupes within 24h');
      } finally { teardown(); }
    },
  },
  {
    name: 'api_error dedup window = 6h (Scrape.do outage)',
    fn: () => {
      setupFreshDb();
      try {
        const r1 = alertBus.raise({
          severity: 'error', category: 'api_error',
          title: 'scrape_do call failed',
          dedupe_key: 'error:scrape_do:12',
        });
        assert.strictEqual(r1.deduped, false);
        const r2 = alertBus.raise({
          severity: 'error', category: 'api_error',
          title: 'scrape_do call failed',
          dedupe_key: 'error:scrape_do:12',
        });
        assert.strictEqual(r2.deduped, true, 're-raise dedupes within 6h');
      } finally { teardown(); }
    },
  },
  {
    name: 'Unknown dedupe_key prefix falls back to default (30 min)',
    fn: () => {
      setupFreshDb();
      try {
        const r1 = alertBus.raise({
          severity: 'info', category: 'other',
          title: 'Some random alert',
          dedupe_key: 'random_key_xyz',
        });
        assert.strictEqual(r1.deduped, false);
        const r2 = alertBus.raise({
          severity: 'info', category: 'other',
          title: 'Some random alert',
          dedupe_key: 'random_key_xyz',
        });
        assert.strictEqual(r2.deduped, true, 'default dedup still works');
      } finally { teardown(); }
    },
  },
  {
    name: 'Same dedupe_key with different severity still dedupes (the dedup is on key, not content)',
    fn: () => {
      setupFreshDb();
      try {
        const r1 = alertBus.raise({
          severity: 'warn', category: 'escalation',
          title: 'Auto-escalate blocked: example.com',
          dedupe_key: 'auto_escalate_blocked:99:1',
        });
        const r2 = alertBus.raise({
          severity: 'error', category: 'escalation',
          title: 'Auto-escalate blocked: example.com (now worse)',
          dedupe_key: 'auto_escalate_blocked:99:1',
        });
        assert.strictEqual(r2.deduped, true, 're-raise with bumped severity still dedupes within window');
        // Confirm the existing row's detail was updated with the count bump
        const existing = alertBus.getById(r1.alert.id);
        const detail = JSON.parse(existing.detail || '{}');
        assert.ok(detail.count >= 2, 'detail.count bumps to track re-occurrences');
      } finally { teardown(); }
    },
  },
  {
    name: 'bulk-acknowledge via admin endpoint: filters by ref_url pattern',
    // We don't need a running server — we can test the underlying SQL
    // directly by inserting alerts and running the same UPDATE.
    fn: () => {
      const ctx = setupFreshDb();
      try {
        // This test verifies the SQL shape, not the HTTP route.
        // The admin route just wraps the same UPDATE.
        const { db } = require('../config/db');
        // setupFreshDb's returned `user` is a hardcoded TEST_ADMIN stub
        // (id=1) — not the real assigned id. After multiple setupFreshDb
        // calls in the same process, AUTOINCREMENT has moved on. Query
        // the actual current admin id.
        const adminId = db.prepare('SELECT id FROM users WHERE email = ?').get('admin@test.local').id;
        // Need a real case to satisfy takedowns FK — create one.
        const now = Date.now();
        const caseInfo = db.prepare(`
          INSERT INTO cases (user_id, victim_name, status, billing_cycle_start, created_at, updated_at)
          VALUES (?, 'Test Case for Bulk Ack', 'open', ?, ?, ?)
        `).run(adminId, now, now, now);
        const caseId = caseInfo.lastInsertRowid;
        // Insert a takedown flagged as test + a real takedown
        const td1 = db.prepare(`
          INSERT INTO takedowns (case_id, user_id, url, domain, status, is_test_data, created_at)
          VALUES (?, ?, 'http://127.0.0.1/x', '127.0.0.1', 'sent', 1, ?)
        `).run(caseId, adminId, now);
        const td2 = db.prepare(`
          INSERT INTO takedowns (case_id, user_id, url, domain, status, is_test_data, created_at)
          VALUES (?, ?, 'https://example.com/y', 'example.com', 'sent', 0, ?)
        `).run(caseId, adminId, now);
        // Insert alerts about both
        const ins = db.prepare(`
          INSERT INTO alerts (ref, severity, category, title, ref_url, created_at)
          VALUES (?, 'warn', 'takedown', ?, ?, ?)
        `);
        ins.run('a1', 'takedown #1 alert', 'takedown:' + td1.lastInsertRowid, now);
        ins.run('a2', 'takedown #2 alert', 'takedown:' + td2.lastInsertRowid, now);
        // Run the bulk-ack-test SQL (the same one in routes/admin.js)
        const result = db.prepare(`
          UPDATE alerts SET acknowledged = 1, ack_by = ?, ack_at = ?
           WHERE acknowledged = 0
             AND (
               (ref_url LIKE 'takedown:%' AND CAST(SUBSTR(ref_url, 10) AS INTEGER) IN (SELECT id FROM takedowns WHERE is_test_data=1))
             )
        `).run(adminId, now);
        assert.strictEqual(result.changes, 1, 'only the test-takedown alert was acked');
        // Verify state
        const testAlert = db.prepare('SELECT acknowledged FROM alerts WHERE ref = ?').get('a1');
        const realAlert = db.prepare('SELECT acknowledged FROM alerts WHERE ref = ?').get('a2');
        assert.strictEqual(testAlert.acknowledged, 1, 'test alert acked');
        assert.strictEqual(realAlert.acknowledged, 0, 'real alert NOT acked');
      } finally { teardown(); }
    },
  },
];

module.exports = tests;