/**
 * routes/cases.js
 * CRUD over cases. Enforces plan + ownership / org RBAC.
 */

'use strict';

const express  = require('express');
const validator = require('validator');
const { requireLogin } = require('../middleware/auth');
const planGate = require('../middleware/planGate');
const Case     = require('../models/case');
const Finding  = require('../models/finding');
const FindingClass = require('../models/findingClassification');
const Takedown = require('../models/takedown');
const Audit    = require('../models/auditLog');
const { getPlan } = require('../config/plans');

const router = express.Router();

// helper: ensure user can access this case (own or same org for admins)
function authoriseCase(req, res, c) {
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  if (req.user.role === 'superadmin') return c;
  if (c.user_id === req.user.id) return c;
  if ((req.user.role === 'admin') && c.org_id && c.org_id === req.user.org_id) return c;
  return res.status(403).json({ error: 'FORBIDDEN' });
}

router.get('/', requireLogin, (req, res) => {
  const list = (req.user.role === 'superadmin')
    ? Case.listAllCases(100, 0)
    : (req.user.role === 'admin' && req.user.org_id
        ? Case.listForOrg(req.user.org_id, 100, 0)
        : Case.listForUser(req.user.id, 100, 0));
  res.json({ cases: list });
});

router.get('/:id', requireLogin, (req, res) => {
  const c = Case.getById(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  const allowed = authoriseCase(req, res, c);
  if (allowed === res) return; // already sent
  const findings  = Finding.listForCase(c.id);
  const takedowns = Takedown.listForCase(c.id);
  res.json({ case: c, findings, takedowns });
});

router.post('/', requireLogin, planGate('case'), (req, res) => {
  const { victim_name, reference_url, context, auto_discover } = req.body || {};
  if (!victim_name || victim_name.length < 2) {
    return res.status(400).json({ error: 'MISSING_VICTIM_NAME' });
  }
  if (reference_url && !validator.isURL(reference_url, { require_protocol: true })) {
    return res.status(400).json({ error: 'INVALID_URL' });
  }
  const c = Case.create({
    user_id: req.user.id,
    org_id: req.user.org_id || null,
    victim_name, reference_url, context,
  });
  Audit.log(req.user, 'case.create', `case:${c.id}`);

  // If the case has no findings yet AND the operator asked for it (or
  // always, for new cases with a victim name), auto-queue a deep discovery
  // scan. This is the right default: a new case has 0 findings until
  // the operator runs something, and "run a scan" is the most useful
  // first action.
  const { db } = require('../config/db');
  const auto = auto_discover !== false; // default true
  if (auto && victim_name.length >= 2) {
    const info = db.prepare(`
      INSERT INTO case_discovery_scans (case_id, user_id, status, progress, created_at)
      VALUES (?, ?, 'queued', 'Auto-queued on case creation', ?)
    `).run(c.id, req.user.id, Date.now());
    Audit.log(req.user, 'case.discover.auto_queue', `case:${c.id}`, { scan_id: info.lastInsertRowid });
    res.json({
      ok: true, case: c,
      auto_discovery: { scan_id: info.lastInsertRowid, status: 'queued', poll_url: `/cases/${c.id}/discover/${info.lastInsertRowid}` },
    });
  } else {
    res.json({ ok: true, case: c });
  }
});

router.patch('/:id', requireLogin, (req, res) => {
  const c = Case.getById(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  if (!authoriseCase(req, res, c)) return;
  const { victim_name, reference_url, context, status } = req.body || {};
  if (reference_url && !validator.isURL(reference_url, { require_protocol: true })) {
    return res.status(400).json({ error: 'INVALID_URL' });
  }
  Case.update(c.id, { victim_name, reference_url, context, status });
  Audit.log(req.user, 'case.update', `case:${c.id}`);
  res.json({ ok: true, case: Case.getById(c.id) });
});

router.post('/:id/close', requireLogin, (req, res) => {
  const c = Case.getById(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  if (!authoriseCase(req, res, c)) return;
  Case.closeCase(c.id);
  Audit.log(req.user, 'case.close', `case:${c.id}`);
  res.json({ ok: true });
});

router.delete('/:id', requireLogin, (req, res) => {
  const c = Case.getById(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  if (req.user.role !== 'superadmin' && c.user_id !== req.user.id) {
    return res.status(403).json({ error: 'FORBIDDEN' });
  }
  Case.removeCase(c.id);
  Audit.log(req.user, 'case.delete', `case:${c.id}`);
  res.json({ ok: true });
});

// ---------- Deep discovery scan ----------
// Kicks off a background scan that:
//   1. Runs ~50 Google queries (name × NCII keyword matrix)
//   2. Validates each hit (HTTP 200 + name-in-body + image probe)
//   3. Runs contactDiscovery + RDAP on each valid domain
//   4. Persists findings + production_pending takedowns (no emails sent)
//
// Returns 202 with a scan_id. Poll GET /cases/:id/discover/:scan_id
// for progress. This is the right way to do a "find every leak site"
// run on a case — no one-off scripts.
router.post('/:id/discover', requireLogin, (req, res) => {
  const c = Case.getById(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  if (req.user.role !== 'superadmin' && c.user_id !== req.user.id && c.org_id !== req.user.org_id) {
    return res.status(403).json({ error: 'FORBIDDEN' });
  }
  if (!c.victim_name || c.victim_name.length < 2) {
    return res.status(400).json({ error: 'MISSING_VICTIM_NAME', message: 'Add a victim name to the case before running a deep discovery scan.' });
  }
  const { db } = require('../config/db');
  // Duplicate-guard: refuse if there's a queued or running scan for
  // this case in the last 5 minutes. Spam-clicking "Run deep discovery"
  // would otherwise burn 5× the Scrape.do quota.
  const since = Date.now() - 5 * 60 * 1000;
  const active = db.prepare(`
    SELECT id, status, created_at FROM case_discovery_scans
     WHERE case_id = ? AND created_at > ? AND status IN ('queued','running')
     ORDER BY created_at DESC LIMIT 1`).get(c.id, since);
  if (active) {
    return res.status(409).json({
      error: 'SCAN_IN_PROGRESS',
      scan_id: active.id,
      status: active.status,
      poll_url: `/cases/${c.id}/discover/${active.id}`,
      message: `A scan is already ${active.status}. Wait for it to finish or cancel it before starting a new one.`,
    });
  }
  const info = db.prepare(`
    INSERT INTO case_discovery_scans (case_id, user_id, status, created_at) VALUES (?, ?, 'queued', ?)
  `).run(c.id, req.user.id, Date.now());
  const scanId = info.lastInsertRowid;
  Audit.log(req.user, 'case.discover.start', `case:${c.id}`, { scan_id: scanId });
  res.status(202).json({
    ok: true, scan_id: scanId, status: 'queued',
    poll_url: `/cases/${c.id}/discover/${scanId}`,
    message: `Discovery scan queued. Poll the URL above for progress.`,
  });
});

router.get('/:id/discover', requireLogin, (req, res) => {
  const c = Case.getById(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  if (req.user.role !== 'superadmin' && c.user_id !== req.user.id && c.org_id !== req.user.org_id) {
    return res.status(403).json({ error: 'FORBIDDEN' });
  }
  const { db } = require('../config/db');
  const scans = db.prepare(`
    SELECT * FROM case_discovery_scans WHERE case_id = ? ORDER BY created_at DESC LIMIT 20
  `).all(c.id);
  res.json({ scans });
});

router.get('/:id/discover/:scan_id', requireLogin, (req, res) => {
  const c = Case.getById(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  if (req.user.role !== 'superadmin' && c.user_id !== req.user.id && c.org_id !== req.user.org_id) {
    return res.status(403).json({ error: 'FORBIDDEN' });
  }
  const { db } = require('../config/db');
  const scan = db.prepare(`SELECT * FROM case_discovery_scans WHERE id = ? AND case_id = ?`).get(parseInt(req.params.scan_id, 10), c.id);
  if (!scan) return res.status(404).json({ error: 'NOT_FOUND' });
  res.json({ scan });
});

router.post('/:id/discover/:scan_id/cancel', requireLogin, (req, res) => {
  const c = Case.getById(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  if (req.user.role !== 'superadmin' && c.user_id !== req.user.id) {
    return res.status(403).json({ error: 'FORBIDDEN' });
  }
  const { db } = require('../config/db');
  const r = db.prepare(`UPDATE case_discovery_scans SET status = 'cancelled', finished_at = ? WHERE id = ? AND case_id = ? AND status IN ('queued','running')`)
    .run(Date.now(), parseInt(req.params.scan_id, 10), c.id);
  if (r.changes === 0) return res.status(400).json({ error: 'NOT_CANCELLABLE' });
  res.json({ ok: true });
});

/**
 * GET /cases/:id/findings
 *
 * List all findings for a case, including each finding's latest
 * AI classification (if any). Used by the evidence workspace on
 * case.html — no plan gate, this is the same scope as the existing
 * case details.
 */
router.get('/:id/findings', requireLogin, (req, res) => {
  const id = parseInt(req.params.id, 10);
  const c = Case.getById(id);
  if (!c) return res.status(404).json({ error: 'CASE_NOT_FOUND' });
  if (req.user.role !== 'superadmin' && c.user_id !== req.user.id) {
    return res.status(403).json({ error: 'FORBIDDEN' });
  }
  const findings = Finding.listForCase(id);
  // Batch-fetch latest classification for all findings at once
  // (single query in the model) and merge.
  const latestByFinding = FindingClass.latestForMany(findings.map(f => f.id));
  const enriched = findings.map(f => ({
    id: f.id,
    url: f.url,
    domain: f.domain,
    page_title: f.page_title,
    page_snippet: f.page_snippet,
    tier: f.tier,
    status: f.status,
    confidence: f.confidence,
    is_ncii: f.is_ncii,
    created_at: f.created_at,
    classification: latestByFinding[f.id] || null,
  }));
  res.json({ ok: true, case_id: id, count: enriched.length, findings: enriched });
});

module.exports = router;
