// Audit trail + error log — append-only JSONL files under data/, capped so
// they never grow unbounded. Powers the superadmin "Activity" section:
// every login, report, admin change and AI question, plus every server
// error (API failures, scheduler errors, anything written to console.error).

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DATA_DIR = path.join(__dirname, '..', 'data');
const ACT_FILE = path.join(DATA_DIR, 'activity.jsonl');
const ERR_FILE = path.join(DATA_DIR, 'errors.jsonl');
const MAX_LINES = 5000;   // rotate threshold
const KEEP_LINES = 3500;  // lines kept after rotation

function appendLine(file, entry) {
  try {
    fs.mkdirSync(DATA_DIR, { recursive: true });
    fs.appendFileSync(file, JSON.stringify(entry) + '\n');
    // Cheap size guard: only count lines when the file is getting big.
    const st = fs.statSync(file);
    if (st.size > 220 * MAX_LINES) {
      const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
      if (lines.length > MAX_LINES) fs.writeFileSync(file, lines.slice(-KEEP_LINES).join('\n') + '\n');
    }
  } catch { /* auditing must never break the app */ }
}

const clip = (v, n) => String(v ?? '').slice(0, n);

// Who did what. `user` is the session user (or an email string for failed
// logins); `detail` is a small object of specifics.
export function logActivity(user, action, detail = {}, req = null) {
  const ip = req ? clip(String(req.headers?.['x-forwarded-for'] || '').split(',')[0].trim()
    || req.socket?.remoteAddress || '', 45) : undefined;
  const d = {};
  for (const [k, v] of Object.entries(detail || {})) {
    if (v === undefined || v === null || v === '') continue;
    d[k] = clip(typeof v === 'object' ? JSON.stringify(v) : v, 180);
  }
  appendLine(ACT_FILE, {
    at: Date.now(),
    user: clip(typeof user === 'string' ? user : user?.email || 'anonymous', 80),
    role: typeof user === 'object' ? user?.role : undefined,
    action: clip(action, 60),
    ...(Object.keys(d).length ? { detail: d } : {}),
    ...(ip ? { ip } : {}),
  });
}

export function logError(source, err, extra = {}) {
  const e = err instanceof Error ? err : new Error(String(err));
  appendLine(ERR_FILE, {
    at: Date.now(),
    source: clip(source, 40),
    message: clip(e.message, 400),
    stack: clip((e.stack || '').split('\n').slice(1, 5).join(' | '), 600),
    ...(Object.keys(extra || {}).length ? { extra: JSON.parse(clip(JSON.stringify(extra), 400)) } : {}),
  });
}

// Mirror every console.error into the error log — this catches scheduler
// failures, DB errors and anything else the codebase already reports,
// without touching each call site. Recursion-guarded.
let hooked = false;
export function hookConsoleErrors() {
  if (hooked) return;
  hooked = true;
  const orig = console.error.bind(console);
  let inside = false;
  console.error = (...args) => {
    orig(...args);
    if (inside) return;
    inside = true;
    try {
      const err = args.find((a) => a instanceof Error);
      const msg = args.map((a) => (a instanceof Error ? a.message : typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ');
      appendLine(ERR_FILE, {
        at: Date.now(), source: 'console',
        message: clip(msg, 400),
        ...(err?.stack ? { stack: clip(err.stack.split('\n').slice(1, 5).join(' | '), 600) } : {}),
      });
    } catch { /* never recurse */ }
    inside = false;
  };
}

// Read a log newest-first with optional text search across the whole entry.
export function readLog(kind, { q = '', limit = 400 } = {}) {
  const file = kind === 'errors' ? ERR_FILE : ACT_FILE;
  let lines = [];
  try { lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean); } catch { return []; }
  const needle = String(q || '').toLowerCase();
  const out = [];
  for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {
    if (needle && !lines[i].toLowerCase().includes(needle)) continue;
    try { out.push(JSON.parse(lines[i])); } catch { /* skip corrupt line */ }
  }
  return out;
}
