/**
 * routes/api.js
 * Agency-plan REST API.
 *
 * Authentication: `Authorization: Bearer <api_token>`.
 *
 * Tokens look like `ncs_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` (32 random
 * base32 chars after the prefix). The plaintext token is shown to the user
 * ONCE at creation; only its SHA-256 hash is stored in the database.
 * Therefore, leaking the DB does not leak valid tokens.
 *
 * Endpoints:
 *   GET    /api/v1/me
 *   POST   /api/v1/api-token/rotate      (generates a new token, invalidates old)
 *   GET    /api/v1/usage
 *   GET    /api/v1/cases
 *   POST   /api/v1/cases
 *   GET    /api/v1/cases/:id
 *   GET    /api/v1/findings
 *   POST   /api/v1/takedowns
 *   GET    /api/v1/takedowns
 */

'use strict';

const crypto  = require('crypto');
const express = require('express');
const { getPlan } = require('../config/plans');
const User    = require('../models/user');
const Case    = require('../models/case');
const Finding = require('../models/finding');
const Takedown = require('../models/takedown');
const { db }  = require('../config/db');
const sec     = require('../config/security');
const apiRateLimit = require('../middleware/rateLimit').api;

const router = express.Router();
router.use(apiRateLimit);

// ----- Token generation + hashing -----
function generateToken() {
  const random = crypto.randomBytes(sec.API_TOKEN_BYTES).toString('base64')
                  .replace(/=+$/, '').replace(/\+/g, '').replace(/\//g, '');
  return `ncs_live_${random.slice(0, 43)}`;     // prefix + 43 chars ≈ 256 bits
}
function hashToken(token) {
  return crypto.createHash('sha256').update(token).digest('hex');
}
function tokenLast4(token) {
  return token ? token.slice(-4) : null;
}

// ----- Public (no bearer) endpoints: token bootstrap + rotate via session
// /api/v1/bootstrap is for first-time agency customers to get their first
// token. It uses the session cookie (post-login) + CSRF, not a bearer token.
router.post('/bootstrap', (req, res) => {
  if (!req.user) return res.status(401).json({ error: 'UNAUTHENTICATED' });
  // Check plan
  const u = db.prepare('SELECT * FROM users WHERE id = ?').get(req.user.id);
  const plan = getPlan(u.plan, u.plan_override);
  if (!plan.api_access) return res.status(402).json({ error: 'AGENCY_PLAN_REQUIRED' });
  // If a non-revoked token already exists, refuse
  const existing = db.prepare('SELECT id FROM api_tokens WHERE user_id = ? AND revoked_at IS NULL').get(req.user.id);
  if (existing) return res.status(409).json({ error: 'TOKEN_EXISTS', message: 'Use POST /api/v1/api-token/rotate to rotate.' });
  // Issue the first token
  const token = generateToken();
  const hash  = hashToken(token);
  db.prepare(`INSERT INTO api_tokens (user_id, token_hash, label, last4, created_at) VALUES (?, ?, ?, ?, ?)`)
    .run(req.user.id, hash, 'primary', tokenLast4(token), Date.now());
  res.status(201).json({
    api_token: token, last4: tokenLast4(token),
    message: 'Save this token now. It will not be shown again.',
  });
});

router.post('/api-token/rotate', (req, res) => {
  // Allow rotation via session OR via a valid (non-revoked) bearer token
  let userId = null;
  if (req.user) userId = req.user.id;
  else if (req.get('authorization')) {
    const h = req.get('authorization') || '';
    const m = /^Bearer\s+([A-Za-z0-9_\-]{20,128})$/.exec(h);
    if (m) {
      const row = db.prepare(`
        SELECT t.user_id, t.revoked_at FROM api_tokens t WHERE t.token_hash = ?
      `).get(hashToken(m[1]));
      if (row && !row.revoked_at) userId = row.user_id;
    }
  }
  if (!userId) return res.status(401).json({ error: 'UNAUTHENTICATED' });
  const token = generateToken();
  const hash  = hashToken(token);
  db.prepare(`UPDATE api_tokens SET revoked_at = ? WHERE user_id = ? AND revoked_at IS NULL`)
    .run(Date.now(), userId);
  db.prepare(`INSERT INTO api_tokens (user_id, token_hash, label, last4, created_at) VALUES (?, ?, ?, ?, ?)`)
    .run(userId, hash, 'primary', tokenLast4(token), Date.now());
  res.json({
    api_token: token, last4: tokenLast4(token),
    message: 'Save this token now. It will not be shown again.',
  });
});

// ----- All endpoints below require a valid bearer token -----
// ----- Auth middleware: look up by hash, not plaintext -----
function authToken(req, res, next) {
  const h = req.get('authorization') || '';
  const m = /^Bearer\s+([A-Za-z0-9_\-]{20,128})$/.exec(h);
  if (!m) return res.status(401).json({ error: 'INVALID_TOKEN' });
  const tokenHash = hashToken(m[1]);
  const row = db.prepare(`
    SELECT t.id AS token_id, t.user_id, t.revoked_at, u.*
      FROM api_tokens t JOIN users u ON u.id = t.user_id
     WHERE t.token_hash = ?
  `).get(tokenHash);
  if (!row || row.revoked_at) return res.status(401).json({ error: 'INVALID_TOKEN' });
  if (row.suspended) return res.status(403).json({ error: 'SUSPENDED' });
  const plan = getPlan(row.plan, row.plan_override);
  if (!plan.api_access) return res.status(402).json({ error: 'API_ACCESS_REQUIRES_AGENCY' });
  req.apiUser = User.safeUser(row);
  req.apiTokenId = row.token_id;
  try {
    db.prepare(`UPDATE api_tokens SET last_used_at = ? WHERE id = ?`).run(Date.now(), row.token_id);
  } catch (_) {}
  next();
}
router.use(authToken);

router.get('/me', (req, res) => {
  res.json({ user: req.apiUser });
});

// ----- Usage summary -----
router.get('/usage', (req, res) => {
  res.json({ plan: getPlan(req.apiUser.plan, req.apiUser.plan_override), usage: usageSummary(req.apiUser.id) });
});

// ----- Cases -----
router.get('/cases', (req, res) => {
  res.json({ cases: Case.listForUser(req.apiUser.id, 100, 0) });
});

router.post('/cases', (req, res) => {
  const { victim_name, reference_url, context } = req.body || {};
  if (!victim_name) return res.status(400).json({ error: 'MISSING_VICTIM_NAME' });
  try { checkLimit(req.apiUser, 'case', 'case'); }
  catch (e) { return res.status(e.status).json({ error: e.code, limit: e.limit, used: e.used }); }
  const c = Case.create({ user_id: req.apiUser.id, victim_name, reference_url, context });
  res.json({ case: c });
});

router.get('/cases/:id', (req, res) => {
  const c = Case.getById(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  if (c.user_id !== req.apiUser.id) return res.status(403).json({ error: 'FORBIDDEN' });
  res.json({ case: c, findings: Finding.listForCase(c.id), takedowns: Takedown.listForCase(c.id) });
});

// ----- Findings -----
router.get('/findings', (req, res) => {
  const rows = db.prepare(`
    SELECT f.* FROM findings f
     WHERE f.user_id = ? AND f.is_ncii = 1
     ORDER BY f.created_at DESC LIMIT 200
  `).all(req.apiUser.id);
  res.json({ findings: rows });
});

// ----- Takedowns (enqueue through the worker for consistency) -----
router.get('/takedowns', (req, res) => {
  res.json({ takedowns: Takedown.listForUser(req.apiUser.id, 200, 0) });
});

router.post('/takedowns', (req, res) => {
  const { case_id, finding_id, url, draft_method } = req.body || {};
  if (!url) return res.status(400).json({ error: 'MISSING_URL' });
  try { checkLimit(req.apiUser, 'takedown', 'takedown'); }
  catch (e) { return res.status(e.status).json({ error: e.code, limit: e.limit, used: e.used }); }
  const c = Case.getById(parseInt(case_id, 10));
  if (!c || c.user_id !== req.apiUser.id) return res.status(404).json({ error: 'CASE_NOT_FOUND' });
  const domain = Finding.domainOf(url);
  const jobId = Takedown.enqueueJob({
    case_id: c.id, user_id: req.apiUser.id, url, domain,
    finding_id: finding_id || null,
    draft_method: draft_method || 'auto',
  });
  res.status(202).json({ job_id: jobId, status: 'queued', poll_url: `/takedown/job/${jobId}` });
});

// ----- helpers -----
function planFor(user) { return getPlan(user.plan, user.plan_override); }
function checkLimit(user, kind, planKey) {
  const plan = planFor(user);
  const limit = plan[`${planKey}_limit`];
  const row = db.prepare(`
    SELECT COUNT(*) AS n FROM ${planKey === 'scan' ? 'scans' : 'takedowns'}
     WHERE user_id = ? AND created_at > ?
  `).get(user.id, Date.now() - 30 * 86400000);
  if (row.n >= limit) {
    const err = new Error(`PLAN_LIMIT:${planKey}`);
    err.code = 'PLAN_LIMIT'; err.status = 402;
    err.limit = limit; err.used = row.n;
    throw err;
  }
}
function usageSummary(userId) {
  const since = Date.now() - 30 * 86400000;
  return {
    scans:    db.prepare('SELECT COUNT(*) AS n FROM scans WHERE user_id = ? AND created_at > ?').get(userId, since).n,
    takedowns:db.prepare('SELECT COUNT(*) AS n FROM takedowns WHERE user_id = ? AND created_at > ?').get(userId, since).n,
    cases:    db.prepare("SELECT COUNT(*) AS n FROM cases WHERE user_id = ? AND status != 'closed'").get(userId).n,
  };
}

module.exports = router;
