/**
 * test/webhook.test.js
 *
 * Verifies the webhook delivery logic without making real network
 * calls. Stubs fetch() with a queue of fake responses.
 */

'use strict';

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

// We need to stub fetch before requiring the webhook module.
const REAL_FETCH = global.fetch;
let mockResponses = [];   // [{ status, body }] — popped in order
function setMock(responses) { mockResponses = responses.slice(); }

function req(method, path, body, headers) {
  return new Promise((resolve, reject) => {
    const data = body ? JSON.stringify(body) : null;
    const http = require('http');
    const r = http.request({
      hostname: '127.0.0.1', port: 3000, method, path,
      headers: Object.assign({ 'content-type': 'application/json', 'content-length': data ? Buffer.byteLength(data) : 0 }, headers || {}),
    }, (res) => {
      let buf = '';
      res.on('data', d => buf += d);
      res.on('end', () => resolve({ status: res.statusCode, body: buf, headers: res.headers }));
    });
    r.on('error', reject);
    if (data) r.write(data);
    r.end();
  });
}

const tests = [
  {
    name: 'webhook: no active webhooks → 0 delivered, 0 failed',
    fn: async () => {
      setupFreshDb();
      try {
        const webhook = require('../services/webhook');
        const r = await webhook.dispatch('test.event', { foo: 1 });
        assert.deepStrictEqual(r, { delivered: 0, failed: 0 });
      } finally { teardown(); }
    },
  },
  {
    name: 'webhook: HTTP 200 → success, recorded, signature correct',
    fn: async () => {
      setupFreshDb();
      try {
        // Stub fetch to always return 200
        const captured = [];
        global.fetch = async (url, opts) => {
          captured.push({ url, body: opts.body, headers: opts.headers });
          return { ok: true, status: 200, text: async () => 'ok' };
        };
        const webhook = require('../services/webhook');
        const db = require('../config/db').db;
        const secret = 'super-secret';
        const created = webhook.create({ name: 'test', url: 'https://example.com/hook', secret, event_filter: 'test.*' });
        const r = await webhook.dispatch('test.ping', { hello: 'world' });
        assert.strictEqual(r.delivered, 1);
        assert.strictEqual(r.failed, 0);
        // Signature header is sha256=<hmac-hex>
        const c = captured[0];
        const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(c.body).digest('hex');
        assert.strictEqual(c.headers['x-ncii-signature'], expected);
        // Delivery was recorded
        const dels = webhook.listDeliveries(created.id);
        assert.strictEqual(dels.length, 1);
        assert.strictEqual(dels[0].status, 200);
        // Failure count is 0
        const w = db.prepare('SELECT * FROM webhooks WHERE id = ?').get(created.id);
        assert.strictEqual(w.failures, 0);
        global.fetch = REAL_FETCH;
      } finally { teardown(); }
    },
  },
  {
    name: 'webhook: HTTP 500 → failure, failure count incremented',
    fn: async () => {
      setupFreshDb();
      try {
        global.fetch = async () => ({ ok: false, status: 500, text: async () => 'oops' });
        const webhook = require('../services/webhook');
        const created = webhook.create({ name: 'test2', url: 'https://example.com/hook2' });
        const r = await webhook.dispatch('any.event', {});
        assert.strictEqual(r.delivered, 0);
        assert.strictEqual(r.failed, 1);
        const w = require('../config/db').db.prepare('SELECT * FROM webhooks WHERE id = ?').get(created.id);
        assert.ok(w, 'webhook should exist in db');
        assert.strictEqual(w.failures, 1);
        global.fetch = REAL_FETCH;
      } finally { teardown(); }
    },
  },
  {
    name: 'webhook: 10 consecutive failures → auto-disabled',
    fn: async () => {
      setupFreshDb();
      try {
        global.fetch = async () => ({ ok: false, status: 502, text: async () => 'bad gateway' });
        const webhook = require('../services/webhook');
        const created = webhook.create({ name: 'test3', url: 'https://example.com/hook3' });
        for (let i = 0; i < 10; i++) await webhook.dispatch('e', {});
        const w = require('../config/db').db.prepare('SELECT * FROM webhooks WHERE id = ?').get(created.id);
        assert.ok(w, 'webhook should exist');
        assert.strictEqual(w.failures, 10);
        assert.strictEqual(w.active, 0, 'webhook should be auto-disabled after 10 failures');
        global.fetch = REAL_FETCH;
      } finally { teardown(); }
    },
  },
  {
    name: 'webhook: event filter respected (only matching events fire)',
    fn: async () => {
      setupFreshDb();
      try {
        let hits = 0;
        global.fetch = async () => { hits++; return { ok: true, status: 200, text: async () => 'ok' }; };
        const webhook = require('../services/webhook');
        webhook.create({ name: 'test4', url: 'https://example.com/hook4', event_filter: 'takedown.removed' });
        await webhook.dispatch('takedown.removed', {});
        await webhook.dispatch('takedown.sent', {});     // should NOT fire
        await webhook.dispatch('case.opened', {});        // should NOT fire
        assert.strictEqual(hits, 1, 'event filter should limit to 1 dispatch');
        global.fetch = REAL_FETCH;
      } finally { teardown(); }
    },
  },
];

module.exports = tests;
