/**
 * middleware/logger.js
 *
 * Tiny structured logger (no pino dep). Writes JSON lines to stdout
 * (and optionally to data/server.log) with:
 *   - request-id correlation (X-Request-Id)
 *   - log level (info, warn, error, debug)
 *   - redacted key_value / password / token / Authorization fields
 *
 * Usage:
 *   const { logger } = require('./middleware/logger');
 *   logger.info('scan.done', { scan_id, validated, queued });
 *   logger.error('scan.failed', { ref, error: e.message });
 *
 * The existing `console.log/warn/error` calls in the codebase continue
 * to work and are mirrored to logger.{info,warn,error} via a
 * shim installed by app.js. New code should call logger.* directly.
 */

'use strict';

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

const REDACT_KEYS = /(password|passwd|secret|token|key|api_?key|authorization|cookie|set-cookie|signature)/i;

function redact(value, depth = 0) {
  if (depth > 4) return '[deep]';
  if (value === null || value === undefined) return value;
  if (typeof value === 'string') {
    // Bare JWT-ish or long opaque strings get a tail
    if (value.length > 32 && /^[A-Za-z0-9_.\-+/=]+$/.test(value)) {
      return value.slice(0, 4) + '…' + value.slice(-4) + ` (len=${value.length})`;
    }
    return value;
  }
  if (typeof value === 'number' || typeof value === 'boolean') return value;
  if (Array.isArray(value)) return value.map(v => redact(v, depth + 1));
  if (typeof value === 'object') {
    const out = {};
    for (const k of Object.keys(value)) {
      if (REDACT_KEYS.test(k) && value[k]) {
        const s = String(value[k]);
        out[k] = s.length <= 6 ? '***' : s.slice(0, 4) + '…' + s.slice(-2);
      } else {
        out[k] = redact(value[k], depth + 1);
      }
    }
    return out;
  }
  return String(value);
}

class Logger {
  constructor() {
    this.level = parseInt(process.env.LOG_LEVEL, 10) || (process.env.NODE_ENV === 'production' ? 2 : 1);  // 1=info, 2=warn, 3=error
    this.filePath = process.env.NCII_LOG_FILE || path.join(__dirname, '..', 'data', 'server.log');
    this.teeToFile = process.env.NCII_LOG_TEE !== '0';     // default ON
    this._reqId = null;
  }

  setRequestId(id) { this._reqId = id; }
  clearRequestId() { this._reqId = null; }

  _emit(level, event, data) {
    if (level < this.level) return;
    const line = JSON.stringify({
      ts: new Date().toISOString(),
      level: ['debug', 'info', 'warn', 'error'][level],
      event,
      req_id: this._reqId,
      ...redact(data || {}),
    });
    process.stdout.write(line + '\n');
    if (this.teeToFile) {
      try { fs.appendFileSync(this.filePath, line + '\n'); } catch (_) { /* ignore */ }
    }
  }
  debug(event, data) { this._emit(1, event, data); }
  info(event, data)  { this._emit(1, event, data); }
  warn(event, data)  { this._emit(2, event, data); }
  error(event, data) { this._emit(3, event, data); }
}

const logger = new Logger();

/**
 * Express middleware: assigns a request id and resets the logger
 * context for each request.
 */
function requestLogger(req, res, next) {
  const id = req.headers['x-request-id'] || crypto.randomBytes(4).toString('hex');
  res.setHeader('X-Request-Id', id);
  req.requestId = id;
  logger.setRequestId(id);
  const start = Date.now();
  res.on('finish', () => {
    logger.info('http', {
      method: req.method,
      path: req.originalUrl,
      status: res.statusCode,
      duration_ms: Date.now() - start,
      user_id: req.user ? req.user.id : null,
    });
    logger.clearRequestId();
  });
  next();
}

module.exports = { logger, requestLogger };
