/**
 * Smoke test for enrollment workflow + bulk marks CSV upload.
 * Runs against a live server on http://127.0.0.1:3001.
 *
 * Usage: node evidence/smoke.js
 */

const fs = require('fs');
const path = require('path');

// Use 'localhost' (resolves to both IPv4 and IPv6); on this Windows host the
// Node listener binds to :: (IPv6 any-address), so 127.0.0.1 fails.
const BASE = 'http://localhost:3001';
const log = (...a) => console.log(...a);
const fail = (msg) => { console.error('FAIL:', msg); process.exitCode = 1; };
const pass = (msg) => log('  PASS:', msg);

async function login(email, password) {
  const r = await fetch(`${BASE}/api/auth/login`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ email, password }),
  });
  const body = await r.json();
  if (!r.ok || !body.token) {
    throw new Error(`login ${email} -> ${r.status} ${JSON.stringify(body)}`);
  }
  return { token: body.token, user: body.user };
}

async function api(method, path, { token, json, formData } = {}) {
  const headers = {};
  if (token) headers['authorization'] = `Bearer ${token}`;
  let body;
  if (json !== undefined) {
    headers['content-type'] = 'application/json';
    body = JSON.stringify(json);
  } else if (formData) {
    body = formData;
  }
  const r = await fetch(`${BASE}${path}`, { method, headers, body });
  let parsed = null;
  const text = await r.text();
  try { parsed = text ? JSON.parse(text) : null; } catch { parsed = text; }
  return { status: r.status, body: parsed };
}

(async () => {
  log('============================================================');
  log('  Smoke test: enrollment workflow + bulk marks CSV upload');
  log(`  Time: ${new Date().toISOString()}`);
  log('============================================================\n');

  // ---- [1] Student self-enrols in class_id=2 (Grade 2) ----
  // student5@school.com (id=10) is seeded in Grade 1 only; safe to enrol in Grade 2.
  log('[1] Student student5@school.com self-enrols in class_id=2 (Grade 2)');
  const student = await login('student5@school.com', 'student123');
  log(`  student token len: ${student.token.length}`);

  const enrol = await api('POST', '/api/enrollments', {
    token: student.token,
    json: { class_id: 2 },
  });
  log(`  HTTP ${enrol.status} -> ${JSON.stringify(enrol.body)}`);
  if (enrol.status !== 201 && enrol.status !== 200) fail('expected 201/200 on enrol');
  if (enrol.body.request_status !== 'pending') fail(`expected request_status=pending, got ${enrol.body.request_status}`);
  if (enrol.body.status !== 'inactive')      fail(`expected status=inactive, got ${enrol.body.status}`);
  if (!enrol.body.requested_at)              fail('expected requested_at to be set');
  if (enrol.body.approved_by !== null)        fail(`expected approved_by=null, got ${enrol.body.approved_by}`);
  pass(`request_status=${enrol.body.request_status}, status=${enrol.body.status}`);
  const enrollmentId = enrol.body.id;

  // ---- [2] Admin GET /api/enrollments/pending ----
  log('\n[2] Admin GET /api/enrollments/pending');
  const admin = await login('admin@school.com', 'admin123');
  const pending = await api('GET', '/api/enrollments/pending', { token: admin.token });
  log(`  HTTP ${pending.status}, ${Array.isArray(pending.body) ? pending.body.length : 'n/a'} rows`);
  if (pending.status !== 200) fail('expected 200 on pending list');
  if (!Array.isArray(pending.body)) fail('expected array');
  const found = pending.body.find(r => r.id === enrollmentId);
  if (!found) fail(`enrollment ${enrollmentId} not found in pending list`);
  else pass(`enrollment ${enrollmentId} visible to admin`);

  // ---- [3] Admin POST /api/enrollments/:id/approve ----
  log('\n[3] Admin POST /api/enrollments/:id/approve');
  const approve = await api('POST', `/api/enrollments/${enrollmentId}/approve`, { token: admin.token });
  log(`  HTTP ${approve.status} -> ${JSON.stringify(approve.body)}`);
  if (approve.status !== 200) fail('expected 200 on approve');
  if (approve.body.request_status !== 'approved') fail('expected request_status=approved');
  if (approve.body.status !== 'active')          fail('expected status=active');
  if (!approve.body.approved_at)                 fail('expected approved_at to be set');
  if (approve.body.approved_by !== admin.user.id) fail('expected approved_by=admin.id');
  pass(`request_status=${approve.body.request_status}, approved_by=${approve.body.approved_by}`);

  // ---- [4] Teacher uploads 3-row valid CSV ----
  log('\n[4] Teacher uploads evidence/valid.csv (3 rows, all valid)');
  const teacher = await login('teacher@school.com', 'teacher123');
  const fd = new FormData();
  fd.append('file', new Blob([fs.readFileSync(path.resolve(__dirname, 'valid.csv'))], { type: 'text/csv' }), 'valid.csv');
  const validRes = await api('POST', '/api/marks/bulk', { token: teacher.token, formData: fd });
  log(`  HTTP ${validRes.status} -> ${JSON.stringify(validRes.body)}`);
  if (validRes.status !== 200) fail('expected 200 on bulk valid');
  if (validRes.body.inserted !== 3) fail(`expected inserted=3, got ${validRes.body.inserted}`);
  if (validRes.body.updated  !== 0) fail(`expected updated=0, got ${validRes.body.updated}`);
  if (!Array.isArray(validRes.body.errors) || validRes.body.errors.length !== 0) fail('expected errors=[]');
  pass(`inserted=${validRes.body.inserted}, updated=${validRes.body.updated}, errors=[]`);

  // ---- [5] Teacher uploads 4-row mixed CSV (2 valid + 2 invalid) ----
  log('\n[5] Teacher uploads evidence/mixed.csv (4 rows: 2 valid, 2 invalid)');
  const fd2 = new FormData();
  fd2.append('file', new Blob([fs.readFileSync(path.resolve(__dirname, 'mixed.csv'))], { type: 'text/csv' }), 'mixed.csv');
  const mixedRes = await api('POST', '/api/marks/bulk', { token: teacher.token, formData: fd2 });
  log(`  HTTP ${mixedRes.status} -> ${JSON.stringify(mixedRes.body)}`);
  if (mixedRes.status !== 200) fail('expected 200 on bulk mixed');
  // 2 valid inserts/updates + 2 errors
  const insUpd = (mixedRes.body.inserted || 0) + (mixedRes.body.updated || 0);
  if (insUpd !== 2) fail(`expected inserted+updated=2, got inserted=${mixedRes.body.inserted} updated=${mixedRes.body.updated}`);
  if (!Array.isArray(mixedRes.body.errors) || mixedRes.body.errors.length !== 2) {
    fail(`expected errors=[2 items], got ${JSON.stringify(mixedRes.body.errors)}`);
  } else {
    const reasons = mixedRes.body.errors.map(e => e.reason).join(' | ');
    pass(`inserted=${mixedRes.body.inserted}, updated=${mixedRes.body.updated}, errors=2 [${reasons}]`);
  }

  // ---- [6] Negative: student cannot call /marks/bulk ----
  log('\n[6] Student attempts POST /api/marks/bulk (should be 403)');
  const fd3 = new FormData();
  fd3.append('file', new Blob([fs.readFileSync(path.resolve(__dirname, 'valid.csv'))], { type: 'text/csv' }), 'valid.csv');
  const stuBulk = await api('POST', '/api/marks/bulk', { token: student.token, formData: fd3 });
  log(`  HTTP ${stuBulk.status} -> ${JSON.stringify(stuBulk.body)}`);
  if (stuBulk.status !== 403) fail(`expected 403, got ${stuBulk.status}`);
  else pass('student correctly blocked from /marks/bulk');

  // ---- [7] GET /api/marks?assignment_id=1 (teacher sees all) ----
  log('\n[7] GET /api/marks?assignment_id=1');
  const marksList = await api('GET', '/api/marks?assignment_id=1', { token: teacher.token });
  log(`  HTTP ${marksList.status}, ${Array.isArray(marksList.body) ? marksList.body.length : 'n/a'} rows`);
  if (marksList.status !== 200) fail('expected 200 on /marks list');
  if (!Array.isArray(marksList.body)) fail('expected array');
  pass(`returned ${marksList.body.length} submission rows for assignment 1`);

  log('\n============================================================');
  log(process.exitCode === 1 ? 'SMOKE: FAIL' : 'SMOKE: PASS');
  log('============================================================');
})().catch(e => { console.error('UNCAUGHT:', e); process.exit(1); });