/**
 * Smoke test — WebSocket hub + role-aware dashboard analytics.
 * Boots against an already-running server on http://localhost:3001.
 *
 * Usage:
 *   node evidence/smoke-ws-dashboard.cjs
 *
 * Output is captured via redirection (printed to stdout) — caller saves to
 * evidence/smoke-ws-dashboard.txt.
 */

const http = require('http');
const WebSocket = require('ws');

const BASE_URL = process.env.BASE_URL || 'http://localhost:3001';
const WS_URL = process.env.WS_URL || 'ws://localhost:3001';

function log(...args) {
  const ts = new Date().toISOString();
  console.log(`[${ts}]`, ...args);
}

function httpRequest(method, path, body, token) {
  return new Promise((resolve, reject) => {
    const url = new URL(path, BASE_URL);
    const data = body ? JSON.stringify(body) : null;
    const opts = {
      method,
      hostname: url.hostname,
      port: url.port || 80,
      path: url.pathname + url.search,
      headers: {
        'Content-Type': 'application/json',
        ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}),
        ...(token ? { 'Authorization': `Bearer ${token}` } : {}),
      },
    };
    const req = http.request(opts, (res) => {
      let buf = '';
      res.on('data', (c) => { buf += c; });
      res.on('end', () => {
        let parsed = null;
        try { parsed = JSON.parse(buf); } catch (_e) { parsed = buf; }
        resolve({ status: res.statusCode, body: parsed });
      });
    });
    req.on('error', reject);
    if (data) req.write(data);
    req.end();
  });
}

async function login(email, password) {
  const r = await httpRequest('POST', '/api/auth/login', { email, password });
  if (r.status !== 200) {
    throw new Error(`Login failed for ${email}: ${r.status} ${JSON.stringify(r.body)}`);
  }
  return r.body;
}

async function timeMs(fn) {
  const t0 = Date.now();
  const r = await fn();
  return { r, ms: Date.now() - t0 };
}

async function main() {
  log('=== WS Hub + Role-aware Dashboard smoke test ===');
  log(`HTTP base: ${BASE_URL}`);
  log(`WS base:   ${WS_URL}`);
  log('');

  // --- 1. Login as each role ----------------------------------------
  log('--- 1. Login as each demo role ---');
  const admin = await login('admin@school.com', 'admin123');
  log(`admin   id=${admin.user.id} role=${admin.user.role}`);
  const teacher = await login('teacher@school.com', 'teacher123');
  log(`teacher id=${teacher.user.id} role=${teacher.user.role}`);
  const student = await login('student@school.com', 'student123');
  log(`student id=${student.user.id} role=${student.user.role}`);
  // Try parent login — fallback to whatever parent account exists in init.js
  let parent;
  try {
    parent = await login('parent@school.com', 'parent123');
    log(`parent  id=${parent.user.id} role=${parent.user.role}`);
  } catch (e) {
    log(`WARN parent@school.com login failed: ${e.message}`);
  }

  // --- 2. WebSocket: invalid JWT rejected ----------------------------
  log('');
  log('--- 2. WS invalid-JWT rejection ---');
  await new Promise((resolve) => {
    const ws = new WebSocket(`${WS_URL}/ws?token=BAD_TOKEN`);
    let resolved = false;
    const finish = (status, msg) => {
      if (resolved) return;
      resolved = true;
      log(`WS(invalid) result: ${status} — ${msg}`);
      try { ws.terminate(); } catch (_) {}
      resolve();
    };
    ws.on('open', () => finish('CONNECTED_BUT_INVALID', 'server accepted bad token (BAD!)'));
    ws.on('error', (err) => finish('REJECTED', `error event: ${err.message || err.code || 'unknown'}`));
    ws.on('unexpected-response', (_req, res) => finish('REJECTED', `HTTP ${res.statusCode}`));
    ws.on('close', (code, reason) => finish('CLOSED', `code=${code} reason=${reason || '(empty)'}`));
    setTimeout(() => finish('TIMEOUT', 'no event within 5s'), 5000);
  });

  // --- 3. WebSocket: valid JWT connects + subscribes -----------------
  log('');
  log('--- 3. WS valid-JWT connect + subscribe ---');
  const wsMessages = [];
  await new Promise((resolve, reject) => {
    const ws = new WebSocket(`${WS_URL}/ws?token=${admin.token}`);
    let resolved = false;
    const finish = (err) => {
      if (resolved) return;
      resolved = true;
      if (err) reject(err);
      else resolve();
      try { ws.close(1000, 'test done'); } catch (_) {}
    };
    ws.on('open', () => {
      log('WS(open): connection established');
      ws.send(JSON.stringify({ type: 'subscribe', channels: ['class:1', 'class:2'] }));
    });
    ws.on('message', (raw) => {
      const msg = JSON.parse(raw.toString());
      wsMessages.push(msg);
      log(`WS(msg): ${JSON.stringify(msg)}`);
      if (msg.type === 'subscribed') {
        // Send a ping to confirm two-way traffic
        setTimeout(() => ws.send(JSON.stringify({ type: 'ping' })), 200);
      }
      if (msg.type === 'pong') {
        // Done after pong received
        setTimeout(() => finish(null), 200);
      }
    });
    ws.on('error', (err) => finish(new Error(`ws error: ${err.message}`)));
    ws.on('close', (code, reason) => {
      if (!resolved) log(`WS(close): code=${code} reason=${reason || '(empty)'}`);
    });
    setTimeout(() => finish(new Error('timeout waiting for pong')), 8000);
  });
  log(`WS(valid) received ${wsMessages.length} message(s):`,
    JSON.stringify(wsMessages.map(m => m.type)));

  // --- 4. Dashboard analytics for each role --------------------------
  log('');
  log('--- 4. Role-aware dashboard analytics ---');
  const roles = [
    { name: 'admin',   token: admin.token,   url: '/api/dashboard/admin' },
    { name: 'teacher', token: teacher.token, url: '/api/dashboard/teacher' },
    { name: 'student', token: student.token, url: '/api/dashboard/student' },
  ];
  if (parent) roles.push({ name: 'parent', token: parent.token, url: '/api/dashboard/parent' });

  const summary = {};
  for (const r of roles) {
    const { r: res, ms } = await timeMs(() =>
      httpRequest('GET', r.url, null, r.token));
    const under500 = ms < 500;
    const ok = res.status === 200 && res.body && typeof res.body === 'object';
    const newFields = pickAnalyticsFields(r.name, res.body || {});
    log(`${r.name.padEnd(8)} status=${res.status} time=${ms}ms under500=${under500}`);
    log(`           analytics fields: ${JSON.stringify(newFields)}`);
    summary[r.name] = {
      status: res.status,
      timeMs: ms,
      under500,
      ok,
      newFields,
      bodyKeys: res.body && typeof res.body === 'object' ? Object.keys(res.body) : [],
    };
    if (!ok) {
      log(`           BODY: ${JSON.stringify(res.body).slice(0, 500)}`);
    }
  }

  // --- 5. Verdict -----------------------------------------------------
  log('');
  log('--- 5. Verdict ---');
  const allOk = Object.values(summary).every(s => s.ok);
  const allUnder500 = Object.values(summary).every(s => s.under500);
  log(`all_ok=${allOk} all_under_500ms=${allUnder500}`);

  const result = {
    pass: allOk && allUnder500,
    summary,
    wsMessages,
  };
  log(`VERDICT: ${result.pass ? 'PASS' : 'FAIL'}`);
  // Output a machine-readable summary on the last line for easy grep
  console.log('===SMOKE_RESULT_JSON===' + JSON.stringify(result));
}

function pickAnalyticsFields(role, body) {
  switch (role) {
    case 'admin':
      return {
        active_enrollments: body.active_enrollments,
        attendance_rate_30d: body.attendance_rate_30d,
        grade_distribution: body.grade_distribution,
        programs_count: body.programs_count,
        classes_count: body.classes_count,
        users_by_role: body.users_by_role,
      };
    case 'teacher':
      return {
        per_course: body.per_course,
        overall_students_count: body.overall_students_count,
        pending_grading_count: body.pending_grading_count,
      };
    case 'student':
      return {
        per_course: body.per_course,
        pending_assignments: body.pending_assignments,
        recent_marks: body.recent_marks,
        gamification_points: body.gamification_points,
        leaderboard_rank: body.leaderboard_rank,
      };
    case 'parent':
      return {
        per_dependent: body.per_dependent,
      };
    default:
      return {};
  }
}

main().catch((err) => {
  console.error('SMOKE TEST CRASH:', err);
  console.log('===SMOKE_RESULT_JSON===' + JSON.stringify({ pass: false, crash: err.message }));
  process.exit(1);
});