// scripts/test-m3-flow.mjs — full M3 integration test.
//
// Walks one Silver mentee through the complete coach-gated flow:
//   1. Hotmart APPROVED webhook → mentee created, accessLevel=course_only
//   2. User confirms L01..L07 → accessLevel=dossier_pending, dossier=not_started
//   3. Coach admin: GET /admin/silver/pending-unlock returns this user
//   4. Coach admin: POST .../trigger-dossier → bot sends intro + first question
//   5. User answers 5 dossier questions → dossier=ready_for_review,
//      career-strategy.json written, completion message sent
//   6. Coach admin: GET /admin/silver/users/:slug returns the dossier inputs
//   7. Coach admin: POST .../unlock-jobbot → mints token, sends jobbot link,
//      accessLevel=full, jobbot mock registers token
//   8. Weekly role push (runWeeklyRolePush) → 5 mock roles sent to group,
//      awaitingRoleReplyFor set on last
//   9. User replies "Applied" → role logged, scheduled +7d status check,
//      appliedRoles[] populated, journey event silver_applied
//  10. Fast-forward time past the +7d check, run runStatusUpdatePrompts →
//      status_check sent, statusPromptsSentToday incremented
//  11. User replies "Got rejected" → status='declined', encouragement msg
//
//   node scripts/test-m3-flow.mjs

import { mkdirSync, rmSync, existsSync, cpSync, readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { tmpdir } from 'os';
import {
  configureTest, _resetTestState, readOutbox, clearOutbox,
} from '../lib/whatsappAdapter.js';
import { handleHotmartWebhook } from '../lib/hotmartWebhook.js';
import { handleSilverMessage } from '../lib/silverCourse.js';
import {
  loadSilver, lessonByDay, markLessonSent, saveSilver,
  ACCESS_LEVELS, scheduleStatusCheck,
} from '../lib/silverStore.js';
import { handleCoachAdminRequest, listPendingUnlock } from '../lib/coachAdmin.js';
import { runWeeklyRolePush, runStatusUpdatePrompts } from '../lib/silverRoles.js';

const REAL = dirname(fileURLToPath(import.meta.url)).replace(/\/scripts$/, '');
const TMP = join(tmpdir(), `ecobot-m3-test-${Date.now()}`);

let pass = 0, fail = 0;
function check(label, ok, detail) {
  if (ok) { pass += 1; console.log(`✅ ${label}`); }
  else    { fail += 1; console.log(`❌ ${label}${detail ? ' — ' + detail : ''}`); }
}

function reqShape(method, url, body = null, headers = {}) {
  const baseHeaders = { authorization: `Bearer ${process.env.WHATSAPP_ORCHESTRATOR_TOKEN}`, ...headers };
  return { method, url, headers: baseHeaders, on: () => {} };
}

async function adminReq(method, path, body = null) {
  const raw = body ? Buffer.from(JSON.stringify(body)) : Buffer.alloc(0);
  return await handleCoachAdminRequest(TMP, reqShape(method, path), raw);
}

async function main() {
  // Setup
  if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true });
  mkdirSync(TMP, { recursive: true });
  cpSync(join(REAL, 'data'), join(TMP, 'data'), { recursive: true });
  configureTest();
  _resetTestState();
  process.env.HOTMART_HOTTOK = 'TEST_HOTTOK';
  process.env.WHATSAPP_ORCHESTRATOR_TOKEN = 'TEST_ORCH_TOKEN';
  delete process.env.JOBBOT_BASE; // Use mock mode for jobbot
  // ECO-41: enable the WhatsApp dossier collector for this end-to-end test.
  // In production the website-strategy flow is the default (env unset);
  // this test specifically validates the fallback WhatsApp dossier path.
  process.env.SILVER_WHATSAPP_DOSSIER = '1';

  // ── 1. Hotmart APPROVED ─────────────────────────────────────────────────
  const fixture = JSON.parse(readFileSync(join(REAL, 'scripts', 'fixtures', 'hotmart-approved.json'), 'utf8'));
  fixture.hottok = 'TEST_HOTTOK';
  let r = await handleHotmartWebhook(TMP, {
    rawBody: Buffer.from(JSON.stringify(fixture)),
    headers: { 'content-type': 'application/json' },
  });
  check('hotmart APPROVED → 200', r.status === 200);
  const slug = r.body.slug;

  // ── 2. L01..L07 + claim Module-1 tasks → dossier_pending ────────────────
  for (let day = 1; day <= 7; day += 1) {
    const lesson = lessonByDay(TMP, day);
    let st = loadSilver(TMP, slug);
    markLessonSent(st, lesson.id);
    saveSilver(TMP, slug, st);
    await handleSilverMessage(TMP, slug, { text: 'Yes', fromNum: '41791234567' });
  }
  // After L07 lessons-only, module 1 is NOT yet complete (tasks pending).
  // Claim all 4 Module-1 tasks then re-trigger the module check by replaying
  // the L07 Yes (idempotent for the lesson, re-runs the module loop).
  let state = loadSilver(TMP, slug);
  for (const id of ['T-W1-career-plan', 'T-W1-cv-v1', 'T-W1-linkedin', 'T-W1-applications']) {
    state.tasks[id] = { taskId: id, status: 'claimed', recordedAt: new Date().toISOString() };
  }
  state.awaitingYesFor = 'L07';
  saveSilver(TMP, slug, state);
  await handleSilverMessage(TMP, slug, { text: 'Yes', fromNum: '41791234567' });
  state = loadSilver(TMP, slug);
  check('after L07 + tasks accessLevel=dossier_pending', state.accessLevel === ACCESS_LEVELS.DOSSIER_PENDING);
  check('module1CompletedAt set', !!state.module1CompletedAt);

  // ── 3. Coach queue surfaces this user ───────────────────────────────────
  let qr = await adminReq('GET', '/admin/silver/pending-unlock');
  check('queue 200', qr.status === 200);
  check('queue includes our user', qr.body.items.some(i => i.slug === slug));
  const queueItem = qr.body.items.find(i => i.slug === slug);
  check('queue item: accessLevel=dossier_pending', queueItem.accessLevel === 'dossier_pending');

  // Auth check: missing token returns 401
  delete process.env.WHATSAPP_ORCHESTRATOR_TOKEN;
  let unauth = await adminReq('GET', '/admin/silver/pending-unlock');
  check('queue without token → 503 (unconfigured)', unauth.status === 503);
  process.env.WHATSAPP_ORCHESTRATOR_TOKEN = 'TEST_ORCH_TOKEN';
  // Wrong token
  const badRaw = Buffer.alloc(0);
  const badResult = await handleCoachAdminRequest(TMP, { method: 'GET', url: '/admin/silver/pending-unlock', headers: { authorization: 'Bearer WRONG' } }, badRaw);
  check('queue with bad token → 401', badResult.status === 401);

  // ── 4. Coach triggers dossier ───────────────────────────────────────────
  clearOutbox();
  r = await adminReq('POST', `/admin/silver/users/${slug}/trigger-dossier`, { coachEmail: 'marco@example.com' });
  check('trigger-dossier 200', r.status === 200);
  check('trigger-dossier returned status=collecting', r.body.status === 'collecting');
  state = loadSilver(TMP, slug);
  check('after trigger: dossier.status=collecting', state.dossier.status === 'collecting');
  check('after trigger: coachTriggeredAt set', !!state.coachTriggeredAt);
  check('after trigger: coachTriggeredBy=marco', state.coachTriggeredBy === 'marco@example.com');
  let outbox = readOutbox().filter(m => m.text);
  check('intro message sent', outbox.some(m => m.text.includes('coach unlocked')));
  check('first question sent', outbox.some(m => m.text.includes('Target roles') || m.text.includes('target roles')));

  // Try triggering again — should 409
  r = await adminReq('POST', `/admin/silver/users/${slug}/trigger-dossier`, { coachEmail: 'marco@example.com' });
  check('re-trigger returns 409', r.status === 409);

  // ── 5. User answers 5 dossier questions ────────────────────────────────
  const answers = [
    'Senior PM in B2B SaaS, Growth Lead',
    'Switzerland on-site or Remote-EU',
    '120k CHF minimum',
    'English fluent, Portuguese native, German B1',
    'No on-call rotation; must allow 2 days remote',
  ];
  // First reply may be a "yes" intro-ack — we go straight to answers since
  // the intro+first-question already shipped.
  for (const a of answers) {
    await handleSilverMessage(TMP, slug, { text: a, fromNum: '41791234567' });
  }
  state = loadSilver(TMP, slug);
  check('5 turns captured', (state.dossier.collectionTurns || []).length === 5);
  check('dossier.status=ready_for_review', state.dossier.status === 'ready_for_review');
  check('strategy file written', existsSync(join(TMP, 'mentees', slug, 'career-strategy.json')));
  const strategy = JSON.parse(readFileSync(join(TMP, 'mentees', slug, 'career-strategy.json'), 'utf8'));
  check('strategy has silverDossier.inputs.targetRoles', !!strategy.silverDossier?.inputs?.targetRoles);

  // ── 6. Detail endpoint returns dossier data ────────────────────────────
  r = await adminReq('GET', `/admin/silver/users/${slug}`);
  check('detail 200', r.status === 200);
  check('detail.state.dossier.status=ready_for_review', r.body.state.dossier.status === 'ready_for_review');

  // ── 7. Coach unlocks jobbot ────────────────────────────────────────────
  clearOutbox();
  r = await adminReq('POST', `/admin/silver/users/${slug}/unlock-jobbot`, { coachEmail: 'marco@example.com' });
  check('unlock-jobbot 200', r.status === 200);
  check('unlock returned token', !!r.body.token);
  check('unlock returned jobbotLink', /\/plan\?t=/.test(r.body.jobbotLink || ''));
  check('jobbotRegistration in mock mode', r.body.jobbotRegistration?.mock === true);
  state = loadSilver(TMP, slug);
  check('accessLevel=full', state.accessLevel === ACCESS_LEVELS.FULL);
  check('coachUnlockedAt set', !!state.coachUnlockedAt);
  check('whatsappToken consumed=null (sent, not yet used)', state.whatsappToken && !state.whatsappToken.consumedAt);
  outbox = readOutbox().filter(m => m.text);
  check('jobbot link message sent', outbox.some(m => /\/plan\?t=/.test(m.text || '')));

  // ── 8. Weekly role push ────────────────────────────────────────────────
  clearOutbox();
  const summary = await runWeeklyRolePush(TMP);
  check('weekly push processed=1', summary.processed === 1);
  check('weekly push sent=5', summary.sent === 5);
  state = loadSilver(TMP, slug);
  check('shownRoles length=5', (state.shownRoles || []).length === 5);
  check('awaitingRoleReplyFor set to last role', state.awaitingRoleReplyFor === 'mock_r_005');
  outbox = readOutbox().filter(m => m.text);
  check('role messages have title+company', outbox.filter(m => /Senior Product Manager|Growth Marketing Lead|Operations Manager/.test(m.text)).length >= 3);

  // ── 9. User replies APPLIED ────────────────────────────────────────────
  clearOutbox();
  await handleSilverMessage(TMP, slug, { text: 'Applied!', fromNum: '41791234567' });
  state = loadSilver(TMP, slug);
  check('appliedRoles length=1', (state.appliedRoles || []).length === 1);
  check('applied role status=applied', state.appliedRoles[0].status === 'applied');
  check('applied role has nextStatusCheckAt', !!state.appliedRoles[0].nextStatusCheckAt);
  const checkIn7d = new Date(state.appliedRoles[0].nextStatusCheckAt) - new Date();
  check('nextStatusCheckAt ~7d out', checkIn7d > 6.5 * 86_400_000 && checkIn7d < 7.5 * 86_400_000, `gap=${checkIn7d}ms`);
  check('shownRoles[mock_r_005].response=applied', state.shownRoles.find(r => r.jobbotRoleId === 'mock_r_005')?.response === 'applied');
  outbox = readOutbox().filter(m => m.text);
  check('applied confirmation sent', outbox.some(m => /logged|Logged/i.test(m.text)));

  // ── 10. Fast-forward to status check time, run prompts ─────────────────
  state = loadSilver(TMP, slug);
  // Force the nextStatusCheckAt into the past by 1 hour
  state.appliedRoles[0].nextStatusCheckAt = new Date(Date.now() - 3600_000).toISOString();
  saveSilver(TMP, slug, state);
  clearOutbox();
  const stPrompt = await runStatusUpdatePrompts(TMP);
  check('status prompt processed=1', stPrompt.processed === 1);
  check('status prompt prompted=1', stPrompt.prompted === 1);
  state = loadSilver(TMP, slug);
  check('lastStatusPromptAt set', !!state.appliedRoles[0].lastStatusPromptAt);
  check('statusPromptsSentToday=1', state.statusPromptsSentToday === 1);
  outbox = readOutbox().filter(m => m.text);
  check('status_check message sent', outbox.some(m => /any updates|status update|application/i.test(m.text)));

  // ── 11. User replies with status update ────────────────────────────────
  clearOutbox();
  await handleSilverMessage(TMP, slug, { text: 'Got rejected by Acme', fromNum: '41791234567' });
  state = loadSilver(TMP, slug);
  check('role status=declined', state.appliedRoles[0].status === 'declined');
  check('statusHistory has declined entry', state.appliedRoles[0].statusHistory.some(h => h.status === 'declined'));
  outbox = readOutbox().filter(m => m.text);
  check('encouragement message sent', outbox.some(m => /Keep moving/i.test(m.text || '')));

  // Cleanup
  rmSync(TMP, { recursive: true, force: true });
  console.log(`\n${pass} passed, ${fail} failed`);
  process.exit(fail === 0 ? 0 : 1);
}

main().catch(e => { console.error(e); process.exit(1); });
