/**
 * Tests for the database layer (src/db.js).
 * Uses an in-memory database so no files are created.
 */

const { test, before, after } = require('node:test');
const assert = require('node:assert/strict');

// Point to an in-memory DB for tests
process.env.DB_PATH = ':memory:';
process.env.ADMIN_USERNAME = 'testadmin';
process.env.ADMIN_PASSWORD = 'testpass';

const db = require('../src/db');

before(() => {
  db.initDB();
});

// ── Config ────────────────────────────────────────────────────────────────────

test('config: getConfig returns null for missing key', () => {
  assert.equal(db.getConfig('nonexistent'), null);
});

test('config: setConfig and getConfig round-trip', () => {
  db.setConfig('test_key', 'hello');
  assert.equal(db.getConfig('test_key'), 'hello');
});

test('config: setConfig overwrites existing value', () => {
  db.setConfig('test_key', 'updated');
  assert.equal(db.getConfig('test_key'), 'updated');
});

test('config: getAllConfig returns object with seeded keys', () => {
  const cfg = db.getAllConfig();
  assert.equal(typeof cfg, 'object');
  assert.ok('device_ip' in cfg);
  assert.ok('poll_interval' in cfg);
});

// ── Admins ────────────────────────────────────────────────────────────────────

test('admins: getAdmin returns seeded admin', () => {
  const admin = db.getAdmin('testadmin');
  assert.ok(admin);
  assert.equal(admin.username, 'testadmin');
  assert.ok(admin.password.startsWith('$2')); // bcrypt hash
});

test('admins: getAdmin returns null for unknown user', () => {
  assert.equal(db.getAdmin('nobody'), null);
});

test('admins: updateAdminPassword changes the hash', () => {
  db.updateAdminPassword('testadmin', 'newpass123');
  const admin = db.getAdmin('testadmin');
  const bcrypt = require('bcryptjs');
  assert.ok(bcrypt.compareSync('newpass123', admin.password));
});

// ── API Tokens ─────────────────────────────────────────────────────────────────

test('tokens: createToken returns token record with token string', () => {
  const tok = db.createToken('Test App');
  assert.ok(tok.id);
  assert.equal(tok.name, 'Test App');
  assert.ok(tok.token.length >= 32);
  assert.equal(tok.active, 1);
});

test('tokens: getTokenByValue returns active token', () => {
  const tok  = db.createToken('App2');
  const found = db.getTokenByValue(tok.token);
  assert.ok(found);
  assert.equal(found.id, tok.id);
});

test('tokens: getTokenByValue returns null for unknown token', () => {
  assert.equal(db.getTokenByValue('no-such-token'), null);
});

test('tokens: toggleToken deactivates token', () => {
  const tok = db.createToken('Deactivate Me');
  db.toggleToken(tok.id, false);
  assert.equal(db.getTokenByValue(tok.token), null);
});

test('tokens: toggleToken reactivates token', () => {
  const tok = db.createToken('Reactivate Me');
  db.toggleToken(tok.id, false);
  db.toggleToken(tok.id, true);
  assert.ok(db.getTokenByValue(tok.token));
});

test('tokens: deleteToken removes it', () => {
  const tok  = db.createToken('Delete Me');
  db.deleteToken(tok.id);
  assert.equal(db.getTokenByValue(tok.token), null);
});

test('tokens: listTokens returns all tokens', () => {
  const before = db.listTokens().length;
  db.createToken('Listed');
  assert.equal(db.listTokens().length, before + 1);
});

// ── Webhooks ──────────────────────────────────────────────────────────────────

test('webhooks: createWebhook stores and returns record', () => {
  const hook = db.createWebhook({ name: 'My Hook', url: 'https://example.com/hook' });
  assert.ok(hook.id);
  assert.equal(hook.name, 'My Hook');
  assert.equal(hook.url, 'https://example.com/hook');
  assert.equal(hook.events, 'received');
  assert.equal(hook.active, 1);
  assert.equal(hook.phone_number, null);
});

test('webhooks: createWebhook with phone filter', () => {
  const hook = db.createWebhook({
    name: 'Filtered Hook', url: 'https://example.com', phone_number: '+46701234567',
  });
  assert.equal(hook.phone_number, '+46701234567');
});

test('webhooks: updateWebhook changes fields', () => {
  const hook    = db.createWebhook({ name: 'Old Name', url: 'https://a.com' });
  const updated = db.updateWebhook(hook.id, { name: 'New Name', events: 'both' });
  assert.equal(updated.name, 'New Name');
  assert.equal(updated.events, 'both');
});

test('webhooks: deleteWebhook removes it', () => {
  const hook   = db.createWebhook({ name: 'Gone', url: 'https://gone.com' });
  const before = db.listWebhooks().length;
  db.deleteWebhook(hook.id);
  assert.equal(db.listWebhooks().length, before - 1);
});

test('webhooks: getActiveWebhooksForEvent returns matching hooks', () => {
  db.createWebhook({ name: 'Catch-all', url: 'https://all.com', events: 'received' });
  const hooks = db.getActiveWebhooksForEvent('received', '+46700000000');
  assert.ok(hooks.length >= 1);
});

test('webhooks: getActiveWebhooksForEvent respects phone filter', () => {
  db.createWebhook({
    name: 'Specific', url: 'https://specific.com',
    phone_number: '+46709999999', events: 'received',
  });
  const matchHooks = db.getActiveWebhooksForEvent('received', '+46709999999');
  const noMatch    = db.getActiveWebhooksForEvent('received', '+46700000001');
  // Specific hook should only appear for its number (catch-alls still included)
  const specificInMatch = matchHooks.some(h => h.name === 'Specific');
  const specificInNone  = noMatch.some(h => h.name === 'Specific');
  assert.ok(specificInMatch);
  assert.ok(!specificInNone);
});

test('webhooks: inactive webhooks not returned for events', () => {
  const hook = db.createWebhook({ name: 'Inactive', url: 'https://inactive.com', events: 'received' });
  db.updateWebhook(hook.id, { active: 0 });
  const hooks = db.getActiveWebhooksForEvent('received', '+0');
  assert.ok(!hooks.some(h => h.id === hook.id));
});

// ── Messages ──────────────────────────────────────────────────────────────────

test('messages: saveMessage stores and returns record', () => {
  const msg = db.saveMessage({
    direction: 'received', phone_number: '+46701111111', message: 'Hello test', status: 'received',
  });
  assert.ok(msg.id);
  assert.equal(msg.direction, 'received');
  assert.equal(msg.message, 'Hello test');
});

test('messages: messageExistsByDeviceId detects duplicates', () => {
  db.saveMessage({
    direction: 'received', phone_number: '+1', message: 'dup', device_msg_id: 'DEV-42',
  });
  assert.ok(db.messageExistsByDeviceId('DEV-42'));
  assert.ok(!db.messageExistsByDeviceId('DEV-99'));
});

test('messages: updateMessageStatus changes status', () => {
  const msg = db.saveMessage({ direction: 'sent', phone_number: '+2', message: 'hi', status: 'pending' });
  db.updateMessageStatus(msg.id, 'sent');
  const rows = db.listMessages({ direction: 'sent' });
  const found = rows.find(r => r.id === msg.id);
  assert.equal(found.status, 'sent');
});

test('messages: listMessages filters by direction', () => {
  db.saveMessage({ direction: 'received', phone_number: '+3', message: 'rx' });
  db.saveMessage({ direction: 'sent',     phone_number: '+3', message: 'tx' });
  const rx = db.listMessages({ direction: 'received' });
  const tx = db.listMessages({ direction: 'sent' });
  assert.ok(rx.every(m => m.direction === 'received'));
  assert.ok(tx.every(m => m.direction === 'sent'));
});

test('messages: listMessages filters by phone_number', () => {
  const phone = '+46799988877';
  db.saveMessage({ direction: 'received', phone_number: phone, message: 'hi' });
  const rows = db.listMessages({ phone_number: phone });
  assert.ok(rows.length >= 1);
  assert.ok(rows.every(m => m.phone_number === phone));
});

test('messages: listMessages respects limit and offset', () => {
  // Insert 5 messages with known phone
  const phone = '+46700000099';
  for (let i = 0; i < 5; i++) {
    db.saveMessage({ direction: 'received', phone_number: phone, message: `msg ${i}` });
  }
  const page1 = db.listMessages({ phone_number: phone, limit: 3, offset: 0 });
  const page2 = db.listMessages({ phone_number: phone, limit: 3, offset: 3 });
  assert.equal(page1.length, 3);
  assert.ok(page2.length >= 1 && page2.length <= 3);
  // No overlap
  const ids1 = new Set(page1.map(m => m.id));
  assert.ok(page2.every(m => !ids1.has(m.id)));
});

test('messages: getMessageStats returns counts', () => {
  const stats = db.getMessageStats();
  assert.ok(typeof stats.sent     === 'number');
  assert.ok(typeof stats.received === 'number');
  assert.ok(typeof stats.failed   === 'number');
  assert.ok(stats.sent >= 0 && stats.received >= 0 && stats.failed >= 0);
});
