// PRE — Per-tool telemetry (append-only JSONL)
// Privacy-preserving: no arguments or results, only hashes, byte counts, and outcomes.

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

const MAX_LOG_BYTES = 100 * 1024 * 1024; // 100 MB
const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000; // 30 days

// Lazy path resolution — allows test mocking of os.homedir()
function logFile() {
  return path.join(os.homedir(), '.pre', 'logs', 'tool_calls.jsonl');
}

function logDir() {
  return path.dirname(logFile());
}

function ensureLogDir() {
  const dir = logDir();
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}

/**
 * Record a tool call. Never blocks the tool loop — synchronous append.
 * @param {object} entry
 * @param {string} entry.session - Session ID
 * @param {string} entry.tool - Tool name
 * @param {object} [entry.args] - Tool arguments (hashed, not stored)
 * @param {string} entry.outcome - 'ok' | 'error' | 'repair' | 'timeout' | 'blocked' | 'cancelled'
 * @param {number} entry.latency_ms - Execution time in ms
 * @param {string} [entry.result] - Tool output (only byte count stored)
 * @param {string} [entry.tier] - Router tier used
 * @param {string} [entry.error_class] - Error classification
 */
function record(entry) {
  try {
    const argsStr = entry.args ? JSON.stringify(entry.args) : '';
    const rec = {
      ts: Date.now(),
      session: entry.session || null,
      tool: entry.tool,
      args_hash: argsStr ? 'sha256:' + crypto.createHash('sha256').update(argsStr).digest('hex').slice(0, 16) : null,
      outcome: entry.outcome || 'ok',
      latency_ms: Math.round(entry.latency_ms || 0),
      result_bytes: entry.result ? Buffer.byteLength(String(entry.result)) : 0,
      tier: entry.tier || null,
      error_class: entry.error_class || null,
    };
    ensureLogDir();
    fs.appendFileSync(logFile(), JSON.stringify(rec) + '\n');
  } catch {
    // Telemetry must never break the tool loop
  }
}

/**
 * Read all log entries (with optional time filter).
 * @param {object} [opts]
 * @param {number} [opts.since] - Unix timestamp (ms) — only entries after this
 * @returns {object[]}
 */
function readLog(opts = {}) {
  const file = logFile();
  if (!fs.existsSync(file)) return [];
  const lines = fs.readFileSync(file, 'utf-8').split('\n').filter(Boolean);
  const entries = [];
  for (const line of lines) {
    try {
      const e = JSON.parse(line);
      if (opts.since && e.ts < opts.since) continue;
      entries.push(e);
    } catch { continue; }
  }
  return entries;
}

/**
 * Compute stats from the telemetry log.
 * @param {object} [opts]
 * @param {string} [opts.tool] - Filter to a specific tool
 * @param {number} [opts.days] - Limit to last N days (default: all)
 * @returns {object}
 */
function stats(opts = {}) {
  const since = opts.days ? Date.now() - (opts.days * 24 * 60 * 60 * 1000) : 0;
  const entries = readLog({ since });

  if (opts.tool) {
    const toolEntries = entries.filter(e => e.tool === opts.tool);
    return toolDrilldown(opts.tool, toolEntries);
  }

  // Aggregate per-tool
  const tools = {};
  for (const e of entries) {
    if (!tools[e.tool]) {
      tools[e.tool] = { calls: 0, ok: 0, errors: 0, repairs: 0, timeouts: 0, blocked: 0, cancelled: 0, total_latency: 0 };
    }
    const t = tools[e.tool];
    t.calls++;
    t.total_latency += e.latency_ms || 0;
    switch (e.outcome) {
      case 'ok': t.ok++; break;
      case 'error': t.errors++; break;
      case 'repair': t.repairs++; break;
      case 'timeout': t.timeouts++; break;
      case 'blocked': t.blocked++; break;
      case 'cancelled': t.cancelled++; break;
    }
  }

  const toolList = Object.entries(tools).map(([name, t]) => ({
    name,
    calls: t.calls,
    error_rate: t.calls > 0 ? t.errors / t.calls : 0,
    repair_rate: t.calls > 0 ? t.repairs / t.calls : 0,
    avg_latency_ms: t.calls > 0 ? Math.round(t.total_latency / t.calls) : 0,
    ...t,
  }));

  const sorted = toolList.sort((a, b) => b.calls - a.calls);
  const top10 = sorted.slice(0, 10);
  const bottom10 = sorted.length > 10 ? sorted.slice(-10).reverse() : [];
  const broken = toolList.filter(t => t.calls >= 3 && t.error_rate > 0.2).sort((a, b) => b.error_rate - a.error_rate);
  const repairHeavy = toolList.filter(t => t.calls >= 3 && t.repair_rate > 0.1).sort((a, b) => b.repair_rate - a.repair_rate);

  return {
    total_calls: entries.length,
    unique_tools: toolList.length,
    period: opts.days ? `${opts.days}d` : 'all',
    top10,
    bottom10,
    broken,
    repair_heavy: repairHeavy,
    all_tools: sorted,
  };
}

function toolDrilldown(toolName, entries) {
  if (entries.length === 0) return { tool: toolName, calls: 0, message: 'No telemetry data for this tool.' };

  const outcomes = { ok: 0, error: 0, repair: 0, timeout: 0, blocked: 0, cancelled: 0 };
  let totalLatency = 0;
  const errorClasses = {};
  const daily = {};

  for (const e of entries) {
    outcomes[e.outcome] = (outcomes[e.outcome] || 0) + 1;
    totalLatency += e.latency_ms || 0;
    if (e.error_class) errorClasses[e.error_class] = (errorClasses[e.error_class] || 0) + 1;
    const day = new Date(e.ts).toISOString().slice(0, 10);
    daily[day] = (daily[day] || 0) + 1;
  }

  return {
    tool: toolName,
    calls: entries.length,
    outcomes,
    error_rate: entries.length > 0 ? outcomes.error / entries.length : 0,
    repair_rate: entries.length > 0 ? outcomes.repair / entries.length : 0,
    avg_latency_ms: entries.length > 0 ? Math.round(totalLatency / entries.length) : 0,
    error_classes: errorClasses,
    daily_calls: daily,
  };
}

/**
 * Rotate log if it exceeds size or age limits.
 * Call periodically (e.g. on server start).
 */
function rotate() {
  try {
    const file = logFile();
    if (!fs.existsSync(file)) return;
    const stat = fs.statSync(file);

    // Size-based rotation
    if (stat.size > MAX_LOG_BYTES) {
      const content = fs.readFileSync(file, 'utf-8');
      const lines = content.split('\n').filter(Boolean);
      const keep = lines.slice(Math.floor(lines.length / 2));
      fs.writeFileSync(file, keep.join('\n') + '\n');
      return;
    }

    // Age-based rotation: remove entries older than 30 days
    const cutoff = Date.now() - MAX_AGE_MS;
    const content = fs.readFileSync(file, 'utf-8');
    const lines = content.split('\n').filter(Boolean);
    const kept = [];
    for (const line of lines) {
      try {
        const e = JSON.parse(line);
        if (e.ts >= cutoff) kept.push(line);
      } catch { kept.push(line); }
    }
    if (kept.length < lines.length) {
      fs.writeFileSync(file, kept.join('\n') + '\n');
    }
  } catch {
    // Rotation failure is non-fatal
  }
}

function shutdown() {
  // No-op now that we use appendFileSync, but kept for API compatibility
}

module.exports = { record, readLog, stats, rotate, shutdown };
