// silverRoles — weekly role delivery, role reply handling, and applied-role
// status prompts. Operates on Silver mentees with accessLevel='full'.
//
// Linear: ECO-16 (weekly delivery), ECO-17 (APPLIED/SKIP/INFO replies),
// ECO-18 (status update prompts + tracker sync).

import { existsSync, readdirSync } from 'fs';
import { join } from 'path';
import { sendText } from './whatsappAdapter.js';
import { nowIso } from './clock.js';
import { journeyEvent } from './journey.js';
import {
  loadSilver, saveSilver, ACCESS_LEVELS,
  resolveMessage,
  recordRoleSent, markRoleResponse, markInfoRequested, isRoleAlreadyShown,
  recordAppliedRole, updateAppliedRoleStatus,
  scheduleStatusCheck, recordStatusPromptSent, statusPromptsRemainingToday,
  isPaused,
} from './silverStore.js';
import { getRoles, logApplied, updateTrackerStatus } from './jobbotClient.js';

const WEEKLY_FLOOR = 5;
const WEEKLY_CEILING = 10;

// ── Weekly role push (ECO-16) ───────────────────────────────────────────────
// Run Mondays 09:00 CET via cron in index.js (or manually via dev/admin).
// Iterates every Silver mentee with accessLevel='full', fetches up to 10
// matching roles from Job-bot, and sends ONE message per role (not a digest).
// Skips paused users and dedupes against previously shown roles.

export async function runWeeklyRolePush(scriptDir) {
  const root = join(scriptDir, 'mentees');
  if (!existsSync(root)) return { processed: 0, sent: 0, errors: [] };

  const summary = { processed: 0, sent: 0, errors: [], byUser: {} };

  for (const slug of readdirSync(root)) {
    const sp = join(root, slug, 'silver.json');
    if (!existsSync(sp)) continue;
    const state = loadSilver(scriptDir, slug);
    if (!state) continue;
    if (state.accessLevel !== ACCESS_LEVELS.FULL) continue;
    if (isPaused(state)) continue;

    summary.processed += 1;
    try {
      const sent = await pushRolesToUser(scriptDir, state);
      summary.sent += sent.count;
      summary.byUser[slug] = { sent: sent.count, thinPipeline: sent.thinPipeline };
    } catch (err) {
      summary.errors.push({ slug, error: err.message });
      journeyEvent(scriptDir, slug, 'silver_weekly_roles_error', { error: err.message });
    }
  }
  return summary;
}

async function pushRolesToUser(scriptDir, state) {
  const slug = state.slug;
  const exclude = (state.shownRoles || []).map(r => r.jobbotRoleId);
  const fetched = await getRoles(slug, { limit: WEEKLY_CEILING, excludeJobbotRoleIds: exclude });
  const roles = (fetched.roles || []).slice(0, WEEKLY_CEILING);
  const thinPipeline = roles.length < WEEKLY_FLOOR;

  if (thinPipeline) {
    journeyEvent(scriptDir, slug, 'silver_thin_pipeline_warning', { count: roles.length, floor: WEEKLY_FLOOR });
  }

  let count = 0;
  for (const role of roles) {
    if (isRoleAlreadyShown(state, role.jobbotRoleId)) continue;  // belt+braces: also catch races
    await sendText(state.groupJid, resolveMessage(scriptDir, state, 'role_offer', {
      title: role.title || '(unknown)',
      company: role.company || '',
      location: role.location || '',
      lessonUrl: role.applyUrl || '',
    }));
    recordRoleSent(state, role);
    journeyEvent(scriptDir, slug, 'silver_role_sent', { jobbotRoleId: role.jobbotRoleId, title: role.title });
    count += 1;
  }
  state.lastRoleBatchAt = nowIso();
  saveSilver(scriptDir, slug, state);
  return { count, thinPipeline };
}

// ── Role reply intent detection (ECO-17) ────────────────────────────────────

const APPLIED_RX = /\b(applied|i\s+applied|applied!?|done!?|sent|submitted)\b/i;
const SKIP_RX    = /\b(skip|skipped|pass|nope?|not\s+(this|interested))\b/i;
const INFO_RX    = /\b(info|more|details?|tell\s+me\s+more|what.+about)\b/i;

export function classifyRoleReply(text) {
  const t = (text || '').trim();
  if (!t) return null;
  // Order matters: APPLIED is most consequential, classify it first.
  if (APPLIED_RX.test(t)) return 'applied';
  if (SKIP_RX.test(t)) return 'skipped';
  if (INFO_RX.test(t)) return 'info';
  return null;
}

// ── Role reply handling (ECO-17) ────────────────────────────────────────────
// Returns { handled: bool, action } so silverCourse.handleSilverMessage()
// can short-circuit Q&A when a reply matches.

export async function handleRoleReply(scriptDir, state, text) {
  const intent = classifyRoleReply(text);
  if (!intent) return { handled: false };

  const targetId = state.awaitingRoleReplyFor;
  if (!targetId) return { handled: false }; // no role context

  const shown = (state.shownRoles || []).find(r => r.jobbotRoleId === targetId);
  if (!shown) return { handled: false };

  // Look up the original role meta from the latest fetched batch by re-asking
  // the Job-bot for tracker data; if not yet available, reconstruct from the
  // shownRoles entry (we don't have title/company stored locally — fetch on
  // APPLIED so the tracker write has a complete payload).
  if (intent === 'applied') {
    const roleMeta = await fetchRoleMetaForApplied(state, targetId);
    markRoleResponse(state, targetId, 'applied');
    recordAppliedRole(state, {
      jobbotRoleId: targetId,
      title: roleMeta.title,
      company: roleMeta.company,
      location: roleMeta.location,
      applyUrl: roleMeta.applyUrl,
    });
    scheduleStatusCheck(state, targetId, 7);
    saveSilver(scriptDir, state.slug, state);

    // Push to Job-bot tracker (best-effort — failure logged but not fatal)
    try {
      await logApplied({ userId: state.slug, jobbotRoleId: targetId });
    } catch (err) {
      journeyEvent(scriptDir, state.slug, 'silver_tracker_applied_failed', { jobbotRoleId: targetId, error: err.message });
    }

    await sendText(state.groupJid, resolveMessage(scriptDir, state, 'applied_confirmed', {
      title: roleMeta.title,
      company: roleMeta.company,
    }));
    journeyEvent(scriptDir, state.slug, 'silver_applied', { jobbotRoleId: targetId });
    return { handled: true, action: 'applied' };
  }

  if (intent === 'skipped') {
    markRoleResponse(state, targetId, 'skipped');
    saveSilver(scriptDir, state.slug, state);
    journeyEvent(scriptDir, state.slug, 'silver_role_skipped', { jobbotRoleId: targetId });
    // Acknowledge silently — no message back. (Per project plan: SKIP = no
    // further action. We could send a one-liner; keeping it quiet keeps the
    // group calm.)
    return { handled: true, action: 'skipped' };
  }

  if (intent === 'info') {
    markInfoRequested(state, targetId);
    saveSilver(scriptDir, state.slug, state);
    const roleMeta = await fetchRoleMetaForApplied(state, targetId);
    await sendText(state.groupJid, resolveMessage(scriptDir, state, 'role_info_followup', {
      title: roleMeta.title,
      company: roleMeta.company,
      description: roleMeta.description || '(no extra description available)',
    }));
    journeyEvent(scriptDir, state.slug, 'silver_role_info', { jobbotRoleId: targetId });
    return { handled: true, action: 'info' };
  }

  return { handled: false };
}

async function fetchRoleMetaForApplied(state, jobbotRoleId) {
  // Fetch the latest batch (the role should still be in the recent batch
  // because the user just replied to a recent message). If not, return
  // best-effort empty meta.
  try {
    const fetched = await getRoles(state.slug, { limit: WEEKLY_CEILING });
    const found = (fetched.roles || []).find(r => r.jobbotRoleId === jobbotRoleId);
    if (found) return found;
  } catch { /* fall through */ }
  return { jobbotRoleId, title: '(role)', company: '', location: '', applyUrl: '', description: '' };
}

// ── Status update prompts (ECO-18) ──────────────────────────────────────────
// Run periodically (e.g. once a day at 10:00 CET). Iterates every Silver
// mentee with applied roles whose nextStatusCheckAt is in the past, and
// sends a status_check prompt — capped at 3/day per user.

export async function runStatusUpdatePrompts(scriptDir) {
  const root = join(scriptDir, 'mentees');
  if (!existsSync(root)) return { processed: 0, prompted: 0, errors: [] };

  const summary = { processed: 0, prompted: 0, errors: [] };
  for (const slug of readdirSync(root)) {
    const sp = join(root, slug, 'silver.json');
    if (!existsSync(sp)) continue;
    const state = loadSilver(scriptDir, slug);
    if (!state) continue;
    if (state.accessLevel !== ACCESS_LEVELS.FULL) continue;
    if (isPaused(state)) continue;

    summary.processed += 1;

    const due = (state.appliedRoles || []).filter(r => {
      if (!r.nextStatusCheckAt) return false;
      if (['offer', 'declined', 'withdrawn'].includes(r.status)) return false;
      return new Date(r.nextStatusCheckAt).getTime() <= Date.now();
    });

    for (const role of due) {
      if (statusPromptsRemainingToday(state) <= 0) break;
      try {
        await sendText(state.groupJid, resolveMessage(scriptDir, state, 'status_check', {
          company: role.company || '(company)',
          title: role.title || '',
        }));
        recordStatusPromptSent(state, role.jobbotRoleId);
        // Re-schedule next check 7 days out from now (cadence per ECO-18).
        scheduleStatusCheck(state, role.jobbotRoleId, 7);
        journeyEvent(scriptDir, slug, 'silver_status_prompt', { jobbotRoleId: role.jobbotRoleId });
        summary.prompted += 1;
      } catch (err) {
        summary.errors.push({ slug, jobbotRoleId: role.jobbotRoleId, error: err.message });
      }
    }
    saveSilver(scriptDir, slug, state);
  }
  return summary;
}

// Status reply classifier — applied to free-text replies in the role-tracking
// context. Maps to the status pipeline. Uses regex first; in production the
// fallback is GPT-4.1-mini intent classification (left as TODO behind a flag
// to keep this dependency-light for now).

const STATUS_PATTERNS = [
  { status: 'offer',       rx: /\b(offer|got\s+the\s+job|accepted|hired)\b/i },
  { status: 'interview',   rx: /\b(interview|onsite|loop|technical\s+round)\b/i },
  { status: 'screening',   rx: /\b(screen|screening|phone\s+screen|recruiter\s+call)\b/i },
  { status: 'declined',    rx: /\b(rejected|declined|no\s+(response|reply)|ghosted|denied)\b/i },
  { status: 'withdrawn',   rx: /\b(withdraw|withdrew|pulled|backed\s+out)\b/i },
  { status: 'applied',     rx: /\b(still\s+waiting|no\s+update|nothing\s+yet|crickets)\b/i },
];

export function classifyStatusReply(text) {
  const t = (text || '').trim();
  if (!t) return null;
  for (const p of STATUS_PATTERNS) {
    if (p.rx.test(t)) return p.status;
  }
  return null;
}

// Apply a status reply to the most recently prompted role. Returns the role
// state plus whether we should send a celebration / encouragement message.

export async function handleStatusReply(scriptDir, state, text) {
  const status = classifyStatusReply(text);
  if (!status) return { handled: false };
  // Find the role that was last prompted (most recent lastStatusPromptAt).
  const role = (state.appliedRoles || [])
    .filter(r => r.lastStatusPromptAt)
    .sort((a, b) => (b.lastStatusPromptAt || '').localeCompare(a.lastStatusPromptAt || ''))[0];
  if (!role) return { handled: false };

  updateAppliedRoleStatus(state, role.jobbotRoleId, status, text.slice(0, 200));
  saveSilver(scriptDir, state.slug, state);
  try {
    await updateTrackerStatus({
      userId: state.slug, jobbotRoleId: role.jobbotRoleId, status,
      note: text.slice(0, 500),
    });
  } catch (err) {
    journeyEvent(scriptDir, state.slug, 'silver_tracker_status_failed', { jobbotRoleId: role.jobbotRoleId, error: err.message });
  }

  journeyEvent(scriptDir, state.slug, 'silver_status_updated', { jobbotRoleId: role.jobbotRoleId, status });

  // Light-touch reactions: celebrate offers, encourage on declined.
  if (status === 'offer') {
    await sendText(state.groupJid, `🎯 An OFFER. Massive. Want me to share this for a community testimonial later? Otherwise, all yours — focus on the negotiation.`);
  } else if (status === 'declined') {
    await sendText(state.groupJid, `Got it — marked as declined. Keep moving. The pipeline matters more than any single role.`);
  }

  return { handled: true, status, jobbotRoleId: role.jobbotRoleId };
}
