/**
 * Integration tests for the REST API routes.
 * Spins up the Express app against an in-memory DB.
 */

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

// Use in-memory DB for tests
process.env.DB_PATH         = ':memory:';
process.env.ADMIN_USERNAME  = 'admin';
process.env.ADMIN_PASSWORD  = 'testpass123';
process.env.SESSION_SECRET  = 'test-secret';
process.env.DEVICE_IP       = '';   // No real device – send/poll will fail gracefully

const { initDB }  = require('../src/db');
const apiRoutes   = require('../src/routes/api');

// Build a minimal Express app for testing
const express        = require('express');
const session        = require('express-session');
const app            = express();
app.use(express.json());
app.use(session({ secret: 'test-secret', resave: false, saveUninitialized: false }));
app.use('/api', apiRoutes);

let server;
let baseURL;

// Cookie jar for session
let sessionCookie = '';

before(async () => {
  initDB();
  server = http.createServer(app);
  await new Promise(r => server.listen(0, '127.0.0.1', r));
  const { port } = server.address();
  baseURL = `http://127.0.0.1:${port}`;
});

after(() => {
  server?.close();
});

// ── Helpers ───────────────────────────────────────────────────────────────────

async function req(method, path, body, extraHeaders = {}) {
  const url     = baseURL + '/api' + path;
  const headers = { 'Content-Type': 'application/json', ...extraHeaders };
  if (sessionCookie) headers['Cookie'] = sessionCookie;

  const response = await fetch(url, {
    method,
    headers,
    body: body !== undefined ? JSON.stringify(body) : undefined,
  });

  // Capture Set-Cookie
  const setCookie = response.headers.get('set-cookie');
  if (setCookie) sessionCookie = setCookie.split(';')[0];

  const text = await response.text();
  let json;
  try { json = JSON.parse(text); } catch { json = text; }
  return { status: response.status, body: json };
}

// ── Auth ──────────────────────────────────────────────────────────────────────

test('auth: /me returns unauthenticated when no session', async () => {
  sessionCookie = '';
  const { status, body } = await req('GET', '/auth/me');
  assert.equal(status, 200);
  assert.equal(body.authenticated, false);
});

test('auth: login with wrong password returns 401', async () => {
  const { status } = await req('POST', '/auth/login', { username: 'admin', password: 'wrong' });
  assert.equal(status, 401);
});

test('auth: login with correct credentials returns 200', async () => {
  const { status, body } = await req('POST', '/auth/login', { username: 'admin', password: 'testpass123' });
  assert.equal(status, 200);
  assert.equal(body.success, true);
});

test('auth: /me returns authenticated after login', async () => {
  const { status, body } = await req('GET', '/auth/me');
  assert.equal(status, 200);
  assert.equal(body.authenticated, true);
  assert.equal(body.username, 'admin');
});

test('auth: change password with wrong current password returns 401', async () => {
  const { status } = await req('PUT', '/auth/password', {
    currentPassword: 'wrongpass', newPassword: 'newpass123',
  });
  assert.equal(status, 401);
});

test('auth: change password succeeds with correct current password', async () => {
  const { status } = await req('PUT', '/auth/password', {
    currentPassword: 'testpass123', newPassword: 'newpass456',
  });
  assert.equal(status, 200);
  // Change back
  await req('PUT', '/auth/password', { currentPassword: 'newpass456', newPassword: 'testpass123' });
});

test('auth: logout destroys session', async () => {
  await req('POST', '/auth/logout');
  const { body } = await req('GET', '/auth/me');
  assert.equal(body.authenticated, false);
  // Log back in for subsequent tests
  await req('POST', '/auth/login', { username: 'admin', password: 'testpass123' });
});

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

test('config: GET /config requires session', async () => {
  const saved = sessionCookie;
  sessionCookie = '';
  const { status } = await req('GET', '/config');
  assert.equal(status, 401);
  sessionCookie = saved;
});

test('config: GET /config returns config object', async () => {
  const { status, body } = await req('GET', '/config');
  assert.equal(status, 200);
  assert.ok('device_ip' in body);
  assert.ok('poll_interval' in body);
});

test('config: PUT /config updates values', async () => {
  const { status } = await req('PUT', '/config', { device_ip: '10.0.0.1', poll_interval: '60' });
  assert.equal(status, 200);
  const { body } = await req('GET', '/config');
  assert.equal(body.device_ip, '10.0.0.1');
  assert.equal(body.poll_interval, '60');
});

// ── Stats ─────────────────────────────────────────────────────────────────────

test('stats: GET /stats returns numeric counts', async () => {
  const { status, body } = await req('GET', '/stats');
  assert.equal(status, 200);
  assert.ok(typeof body.sent     === 'number');
  assert.ok(typeof body.received === 'number');
  assert.ok(typeof body.failed   === 'number');
});

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

test('tokens: POST /tokens creates token', async () => {
  const { status, body } = await req('POST', '/tokens', { name: 'Test Token' });
  assert.equal(status, 201);
  assert.ok(body.id);
  assert.equal(body.name, 'Test Token');
  assert.ok(body.token.length >= 32);
});

test('tokens: GET /tokens lists tokens', async () => {
  const { status, body } = await req('GET', '/tokens');
  assert.equal(status, 200);
  assert.ok(Array.isArray(body));
  assert.ok(body.length >= 1);
});

test('tokens: PATCH /tokens/:id toggles active state', async () => {
  const { body: tok } = await req('POST', '/tokens', { name: 'Toggle Me' });
  const { status }    = await req('PATCH', `/tokens/${tok.id}`, { active: false });
  assert.equal(status, 200);
});

test('tokens: DELETE /tokens/:id removes token', async () => {
  const { body: tok }  = await req('POST', '/tokens', { name: 'Delete Me' });
  const { status }     = await req('DELETE', `/tokens/${tok.id}`);
  assert.equal(status, 200);
});

test('tokens: API token auth works on /sms/messages', async () => {
  const { body: tok } = await req('POST', '/tokens', { name: 'API Client' });
  const saved = sessionCookie;
  sessionCookie = '';
  const { status } = await req('GET', '/sms/messages', undefined, {
    Authorization: `Bearer ${tok.token}`,
  });
  assert.equal(status, 200);
  sessionCookie = saved;
});

test('tokens: invalid Bearer token returns 401', async () => {
  const saved = sessionCookie;
  sessionCookie = '';
  const { status } = await req('GET', '/sms/messages', undefined, {
    Authorization: 'Bearer invalid-token-xyz',
  });
  assert.equal(status, 401);
  sessionCookie = saved;
});

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

test('webhooks: POST /webhooks creates webhook', async () => {
  const { status, body } = await req('POST', '/webhooks', {
    name: 'My Hook', url: 'https://example.com/hook',
  });
  assert.equal(status, 201);
  assert.ok(body.id);
  assert.equal(body.name, 'My Hook');
});

test('webhooks: POST /webhooks requires name and url', async () => {
  const { status } = await req('POST', '/webhooks', { name: 'No URL' });
  assert.equal(status, 400);
});

test('webhooks: GET /webhooks lists webhooks', async () => {
  const { status, body } = await req('GET', '/webhooks');
  assert.equal(status, 200);
  assert.ok(Array.isArray(body));
});

test('webhooks: PUT /webhooks/:id updates webhook', async () => {
  const { body: hook }  = await req('POST', '/webhooks', { name: 'Original', url: 'https://a.com' });
  const { status, body } = await req('PUT', `/webhooks/${hook.id}`, { name: 'Updated' });
  assert.equal(status, 200);
  assert.equal(body.name, 'Updated');
});

test('webhooks: DELETE /webhooks/:id removes webhook', async () => {
  const { body: hook } = await req('POST', '/webhooks', { name: 'Gone', url: 'https://b.com' });
  const { status }     = await req('DELETE', `/webhooks/${hook.id}`);
  assert.equal(status, 200);
});

// ── SMS Messages ──────────────────────────────────────────────────────────────

test('sms: GET /sms/messages returns array', async () => {
  const { status, body } = await req('GET', '/sms/messages');
  assert.equal(status, 200);
  assert.ok(Array.isArray(body));
});

test('sms: GET /sms/messages/received returns only received', async () => {
  const { body } = await req('GET', '/sms/messages/received');
  assert.ok(Array.isArray(body));
  assert.ok(body.every(m => m.direction === 'received'));
});

test('sms: GET /sms/messages/sent returns only sent', async () => {
  const { body } = await req('GET', '/sms/messages/sent');
  assert.ok(Array.isArray(body));
  assert.ok(body.every(m => m.direction === 'sent'));
});

test('sms: POST /sms/send with no device configured returns 502', async () => {
  // Device IP is empty – should fail gracefully
  const { status, body } = await req('POST', '/sms/send', {
    number: '+46701234567', message: 'Test message',
  });
  assert.equal(status, 502);
  assert.ok(body.error);
});

test('sms: POST /sms/send missing fields returns 400', async () => {
  const { status } = await req('POST', '/sms/send', { number: '+46701234567' });
  assert.equal(status, 400);
});

test('sms: POST /sms/send unauthenticated returns 401', async () => {
  const saved = sessionCookie;
  sessionCookie = '';
  const { status } = await req('POST', '/sms/send', { number: '+1', message: 'hi' });
  assert.equal(status, 401);
  sessionCookie = saved;
});
