/**
 * routes/findings.js
 *
 * Operator-visible evidence workspace for a single finding.
 * All endpoints are plan-gated (Pro + Agency) per the user's spec:
 *   - /findings/:id/page        — fetch + stream the live page HTML
 *   - /findings/:id/save.mhtml  — build + return a self-contained MHTML
 *   - /findings/:id/classify    — AI categorization (manual button)
 *   - /findings/:id/classification — read latest AI classification
 *   - /findings/search?case_id=&q= — cross-findings full-text search
 *
 * Privacy: we never persist the page content. The viewer endpoint
 * streams the live HTML straight to the browser. The MHTML endpoint
 * builds bytes in memory and sends them as a download. Both paths
 * can be used in any order without leaving a copy on the server.
 */

'use strict';

const express = require('express');
const path    = require('path');
const router  = express.Router();
const { requireLogin, requirePlan } = require('../middleware/auth');
const Finding = require('../models/finding');
const FindingClass = require('../models/findingClassification');
const ai      = require('../services/ai');
const mhtmlBuilder = require('../services/mhtmlBuilder');
const alertBus = require('../services/alertBus');
const { publicBaseUrl } = require('../config/publicBaseUrl');

// All endpoints are plan-gated at Pro + Agency. The plan is checked
// against the LOGGED-IN user (req.user), not the case owner — the
// plan belongs to the operator who is paying for the feature.
const gate = [requireLogin, requirePlan('pro', 'agency')];

/**
 * GET /findings/:id/view?case_id=N
 *
 * Render the in-page viewer (MHTML iframe + search + classify + save).
 * Also plan-gated — the viewer is the main entry point to the
 * new evidence workspace.
 */
router.get('/findings/:id/view', ...gate, (req, res) => {
  const id = parseInt(req.params.id, 10);
  const f = Finding.getById(id);
  if (!f) return res.status(404).send('Finding not found');
  res.setHeader('Cache-Control', 'no-store');
  // Inline the case_id into the served HTML so the viewer's back link works.
  const caseId = parseInt(req.query.case_id || '0', 10) || f.case_id;
  const html = require('fs').readFileSync(path.join(__dirname, '..', 'public', 'views', 'finding-viewer.html'), 'utf8');
  res.type('text/html').send(html.replace(/CASE_ID/g, String(caseId)));
});

/**
 * GET /findings/:id/page
 *
 * Stream the live page content for a finding. NEVER persists anything.
 * The browser can render it directly or the operator can hit "Save
 * as MHTML" to get a downloadable file.
 */
router.get('/findings/:id/page', ...gate, async (req, res) => {
  const id = parseInt(req.params.id, 10);
  const f = Finding.getById(id);
  if (!f) return res.status(404).json({ error: 'NOT_FOUND' });
  // The plan owner is the case owner, not the operator running the
  // request (operator might be a superadmin previewing).

  try {
    const page = await mhtmlBuilder.fetchPage(f.url);
    res.setHeader('Content-Type', page.contentType || 'text/html; charset=utf-8');
    res.setHeader('Cache-Control', 'no-store');
    res.setHeader('X-Evidence-Source-URL', page.finalUrl);
    res.send(page.html);
  } catch (e) {
    res.status(502).json({ error: 'FETCH_FAILED', message: e.message });
  }
});

/**
 * GET /findings/:id/save.mhtml
 *
 * Build a self-contained MHTML archive in memory and send it as a
 * download. The MHTML includes the rendered HTML + all inline images
 * embedded as base64. Opens in Chrome / Edge / IE11 / mail clients.
 *
 * No file is written to disk. The response is the only copy.
 */
router.get('/findings/:id/save.mhtml', ...gate, async (req, res) => {
  const id = parseInt(req.params.id, 10);
  const f = Finding.getById(id);
  if (!f) return res.status(404).json({ error: 'NOT_FOUND' });

  // Set a generous timeout — MHTML build may need to fetch many images.
  res.setTimeout(120000);

  try {
    const t0 = Date.now();
    const m = await mhtmlBuilder.buildFromUrl(f.url);
    const safeDomain = (f.domain || 'finding').replace(/[^a-z0-9.-]/gi, '_');
    const filename = `ncii-evidence-${id}-${safeDomain}-${new Date().toISOString().slice(0,10)}.mhtml`;

    res.setHeader('Content-Type', m.contentType);
    res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
    res.setHeader('X-Evidence-Parts', String(m.parts));
    res.setHeader('X-Evidence-Build-Ms', String(Date.now() - t0));
    res.setHeader('Cache-Control', 'no-store');
    res.send(m.body);

    alertBus.raise({
      severity: 'info', category: 'evidence_export',
      title: `MHTML export: ${f.domain}`,
      detail: { finding_id: id, parts: m.parts, ms: Date.now() - t0, by: req.user.email },
      dedupe_key: `mhtml:${id}:${req.user.id}`,
    });
  } catch (e) {
    res.status(502).json({ error: 'MHTML_BUILD_FAILED', message: e.message });
  }
});

/**
 * POST /findings/:id/classify
 *
 * Run AI categorization on a finding. Body is optional — if
 * `includeBody: true` is passed, the server will fetch the page
 * first and include the first 800 chars of body text in the
 * prompt. This makes classification more accurate but costs
 * an extra fetch.
 */
router.post('/findings/:id/classify', ...gate, async (req, res) => {
  const id = parseInt(req.params.id, 10);
  const f = Finding.getById(id);
  if (!f) return res.status(404).json({ error: 'NOT_FOUND' });

  const includeBody = !!(req.body && req.body.includeBody);
  let bodyText = null;
  if (includeBody) {
    try {
      const page = await mhtmlBuilder.fetchPage(f.url);
      bodyText = page.html
        .replace(/<script\b[\s\S]*?<\/script>/gi, ' ')
        .replace(/<style\b[\s\S]*?<\/style>/gi, ' ')
        .replace(/<[^>]+>/g, ' ')
        .replace(/&nbsp;/g, ' ')
        .replace(/\s+/g, ' ')
        .trim();
    } catch (_) { /* not fatal — categorize on title+snippet only */ }
  }

  // Look up the case so we can pass the depicted person's name.
  const Case = require('../models/case');
  const c = Case.getById(f.case_id);
  const victimName = c ? c.victim_name : null;

  try {
    const r = await ai.categorize({
      url: f.url,
      title: f.page_title,
      snippet: f.page_snippet,
      bodyText,
      victimName,
    });
    const row = FindingClass.record({
      finding_id: f.id,
      category: r.category,
      confidence: r.confidence,
      evidence: r.evidence,
      summary: r.summary,
      model: r.model,
      ai_call_id: r.aiCallId,
      triggered_by: 'manual',
    });
    res.json({ ok: true, classification: row });
  } catch (e) {
    alertBus.raise({
      severity: 'warn', category: 'ai',
      title: `AI classify failed: ${f.domain}`,
      detail: { finding_id: id, error: e.message },
      dedupe_key: `classify_err:${id}`,
    });
    res.status(502).json({ error: 'CLASSIFY_FAILED', message: e.message });
  }
});

/**
 * GET /findings/:id/classification
 *
 * Read the latest classification for a finding (null if never
 * classified). Returns a single row, not a history.
 */
router.get('/findings/:id/classification', ...gate, (req, res) => {
  const id = parseInt(req.params.id, 10);
  const f = Finding.getById(id);
  if (!f) return res.status(404).json({ error: 'NOT_FOUND' });
  const row = FindingClass.latestFor(f.id);
  res.json({ ok: true, classification: row || null });
});

/**
 * GET /findings/search?case_id=N&q=term
 *
 * Cross-findings full-text search. Searches title + snippet + url +
 * the latest classification evidence + summary for a given case.
 * Operator-scoped (only the user's own cases + their findings).
 */
router.get('/findings/search', ...gate, (req, res) => {
  const caseId = parseInt(req.query.case_id, 10);
  const q = String(req.query.q || '').trim();
  if (!caseId) return res.status(400).json({ error: 'CASE_ID_REQUIRED' });
  if (!q || q.length < 2) return res.status(400).json({ error: 'QUERY_TOO_SHORT', min_length: 2 });
  const Case = require('../models/case');
  const c = Case.getById(caseId);
  if (!c) return res.status(404).json({ error: 'CASE_NOT_FOUND' });
  // Visibility: superadmin sees all, others see their own cases.
  if (req.user.role !== 'superadmin' && c.user_id !== req.user.id) {
    return res.status(403).json({ error: 'FORBIDDEN' });
  }
  const results = FindingClass.searchCase(caseId, q);
  res.json({ ok: true, case_id: caseId, q, count: results.length, results });
});

module.exports = router;
