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

// --- Configuration ---
const TELEMETRY_ENDPOINT = process.env.WALLE_TELEMETRY_URL || 'https://create-walle.fly.dev/api/wall-e/telemetry/ingest';
const FLUSH_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
const MAX_BATCH_SIZE = 100;
const DATA_DIR = process.env.WALL_E_DATA_DIR || path.join(process.env.HOME || '/tmp', '.walle', 'data');
const INSTALL_ID_PATH = path.join(DATA_DIR, '.install-id');
const CLI_LIFECYCLE_PATH = path.join(DATA_DIR, '.cli-lifecycle.jsonl');
const MACHINE_ID_PATH = process.env.WALL_E_MACHINE_ID_PATH || path.join(path.dirname(DATA_DIR), '.machine-id');
const CONFIG_PATH = path.join(__dirname, 'wall-e-config.json');

// --- Opt-out ---
function isDisabled() {
  return process.env.WALLE_TELEMETRY === '0' || process.env.WALLE_TELEMETRY === 'false';
}

// --- Install ID (anonymous, persistent) ---
let _installId = null;
function getInstallId() {
  if (_installId) return _installId;
  try {
    if (fs.existsSync(INSTALL_ID_PATH)) {
      _installId = fs.readFileSync(INSTALL_ID_PATH, 'utf8').trim();
      if (_installId) return _installId;
    }
  } catch {}
  _installId = crypto.randomUUID();
  try {
    if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
    fs.writeFileSync(INSTALL_ID_PATH, _installId, 'utf8');
  } catch {}
  return _installId;
}

// --- Machine bucket (anonymous, stable across reinstall churn) ---
let _machineBucket = null;
function getMachineBucket() {
  if (_machineBucket) return _machineBucket;
  let machineId = '';
  try {
    if (fs.existsSync(MACHINE_ID_PATH)) {
      machineId = fs.readFileSync(MACHINE_ID_PATH, 'utf8').trim();
    }
    if (!machineId) {
      machineId = crypto.randomUUID();
      fs.mkdirSync(path.dirname(MACHINE_ID_PATH), { recursive: true });
      fs.writeFileSync(MACHINE_ID_PATH, machineId, { encoding: 'utf8', mode: 0o600 });
    }
  } catch {
    machineId = getInstallId();
  }
  _machineBucket = crypto.createHash('sha256').update(machineId).digest('hex').slice(0, 16);
  return _machineBucket;
}

function _sanitizeOwnerName(value) {
  if (typeof value !== 'string') return '';
  const cleaned = value.replace(/[\r\n\t]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 200);
  if (!cleaned || cleaned.toLowerCase() === 'unknown') return '';
  return cleaned;
}

function _readConfiguredOwnerName() {
  const envName = _sanitizeOwnerName(process.env.WALLE_OWNER_NAME || '');
  if (envName) return envName;
  try {
    const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
    return _sanitizeOwnerName(cfg?.owner?.name || '');
  } catch {
    return '';
  }
}

function shouldIncludeRawOwnerName() {
  const value = String(process.env.WALLE_TELEMETRY_INCLUDE_OWNER_NAME || '').toLowerCase();
  return value === '1' || value === 'true' || value === 'yes';
}

function getTelemetryUser() {
  const ownerName = _readConfiguredOwnerName();
  if (!ownerName) return null;
  const normalized = ownerName.toLowerCase().replace(/\s+/g, ' ').trim();
  const user = {
    owner_name_hash: crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 16),
  };
  if (shouldIncludeRawOwnerName()) user.owner_name = ownerName;
  return user;
}

// --- Local JSONL log (survives crashes, unlike the in-memory buffer) ---
const LOCAL_LOG_DIR = path.join(process.env.HOME || '/tmp', '.walle', 'logs');
const LOCAL_LOG_PATH = path.join(LOCAL_LOG_DIR, 'telemetry.jsonl');
const LOCAL_LOG_MAX_BYTES = 2 * 1024 * 1024; // 2 MB cap — rotate when exceeded

function _writeLocal(event, meta) {
  try {
    if (!fs.existsSync(LOCAL_LOG_DIR)) fs.mkdirSync(LOCAL_LOG_DIR, { recursive: true });
    // Rotate: if file exceeds cap, rename to .prev (overwrite any existing .prev)
    try {
      const stat = fs.statSync(LOCAL_LOG_PATH);
      if (stat.size > LOCAL_LOG_MAX_BYTES) {
        fs.renameSync(LOCAL_LOG_PATH, LOCAL_LOG_PATH + '.prev');
      }
    } catch {}
    fs.appendFileSync(LOCAL_LOG_PATH, JSON.stringify({ e: event, m: meta || {}, t: Date.now() }) + '\n');
  } catch {}
}

// --- Event buffer ---
let buffer = [];
let flushTimer = null;

function track(event, meta) {
  if (isDisabled()) return;
  // Persist errors locally for post-crash analysis
  if (event === 'error') _writeLocal(event, meta);
  buffer.push({
    e: event,
    m: meta || {},
    t: Date.now(),
  });
  if (buffer.length >= MAX_BATCH_SIZE) flush();
}

async function flush() {
  if (isDisabled() || buffer.length === 0) return;
  const batch = buffer.splice(0, MAX_BATCH_SIZE);
  const version = getVersion();
  const payload = {
    id: getInstallId(),
    machine: getMachineBucket(),
    v: version,
    os: process.platform,
    node: process.version,
    events: batch,
  };
  const user = getTelemetryUser();
  if (user) payload.user = user;
  try {
    const res = await fetch(TELEMETRY_ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
      signal: AbortSignal.timeout(10000),
    });
    if (!res.ok) {
      // Put events back if server rejects — but don't grow unbounded
      if (buffer.length < MAX_BATCH_SIZE * 3) {
        buffer.unshift(...batch);
      }
    }
  } catch {
    // Network error — put events back (bounded)
    if (buffer.length < MAX_BATCH_SIZE * 3) {
      buffer.unshift(...batch);
    }
  }
}

// --- First-run funnel tracking ---
const FUNNEL_PATH = path.join(DATA_DIR, '.telemetry-funnel.json');
const FUNNEL_STEPS = [
  'install',
  'boot',
  'first_ingest',
  'first_chat',
  'first_skill',
  'upgrade_available',
  'upgrade_prompt_shown',
  'upgrade_prompt_dismissed',
  'upgrade_prompt_command_copied',
  'upgrade_apply_clicked',
  'upgrade_apply_started',
  'upgrade_completed',
];

function trackFunnelStep(step) {
  if (isDisabled()) return;
  try {
    let funnel = {};
    try { funnel = JSON.parse(fs.readFileSync(FUNNEL_PATH, 'utf8')); } catch {}
    if (funnel[step]) return; // already recorded
    funnel[step] = Date.now();
    fs.writeFileSync(FUNNEL_PATH, JSON.stringify(funnel, null, 2), 'utf8');

    // Calculate time from install to this step
    const installTime = funnel.install || funnel.boot || Date.now();
    const elapsed = Math.round((Date.now() - installTime) / 1000);
    track('funnel', { step, elapsed_seconds: elapsed, steps_completed: Object.keys(funnel).length });
  } catch {}
}

// --- Version helper ---
let _version = null;
function getVersion() {
  if (_version) return _version;
  try {
    const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
    _version = pkg.version || 'unknown';
  } catch {
    _version = 'unknown';
  }
  return _version;
}

function drainCliLifecycleEvents() {
  if (isDisabled()) return;
  let lines = [];
  try {
    if (!fs.existsSync(CLI_LIFECYCLE_PATH)) return;
    lines = fs.readFileSync(CLI_LIFECYCLE_PATH, 'utf8')
      .split(/\r?\n/)
      .filter(Boolean)
      .slice(-100);
    fs.unlinkSync(CLI_LIFECYCLE_PATH);
  } catch {
    return;
  }
  const allowed = new Set([
    'cli_install_started',
    'cli_install_completed',
    'cli_update_started',
    'cli_update_completed',
  ]);
  for (const line of lines) {
    try {
      const entry = JSON.parse(line);
      if (!entry || !allowed.has(entry.event)) continue;
      const meta = entry.meta && typeof entry.meta === 'object' ? entry.meta : {};
      track(entry.event, {
        ...meta,
        age_seconds: entry.t ? Math.max(0, Math.round((Date.now() - entry.t) / 1000)) : undefined,
      });
    } catch {}
  }
}

// --- Lifecycle ---
function start() {
  if (isDisabled()) return;
  if (flushTimer) return;
  drainCliLifecycleEvents();
  track('startup', {
    uptime: process.uptime(),
  });
  trackFunnelStep('install');
  flushTimer = setInterval(flush, FLUSH_INTERVAL_MS);
  // Don't keep process alive just for telemetry
  if (flushTimer.unref) flushTimer.unref();
  // Flush compat usage after a delay (allow boot-time paths to register)
  setTimeout(() => {
    try {
      const usage = require('./compat').getBootUsage();
      if (Object.keys(usage).length > 0) trackCompatUsage(usage);
    } catch {}
  }, 30000).unref();
}

function stop() {
  if (flushTimer) {
    clearInterval(flushTimer);
    flushTimer = null;
  }
  // Best-effort final flush (fire-and-forget)
  flush().catch(() => {});
}

// --- First-run notice ---
function printNoticeIfFirstRun() {
  if (isDisabled()) return;
  const noticePath = path.join(DATA_DIR, '.telemetry-notice-shown');
  try {
    if (fs.existsSync(noticePath)) return;
    console.log('\n  Wall-E collects pseudonymous usage data to improve the tool.');
    console.log('  No message content or API keys are sent. Owner names are sent only when WALLE_TELEMETRY_INCLUDE_OWNER_NAME=1; otherwise Wall-E may send a one-way owner-name hash.');
    console.log('  To opt out: export WALLE_TELEMETRY=0\n');
    if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
    fs.writeFileSync(noticePath, new Date().toISOString(), 'utf8');
  } catch {}
}

/**
 * Track an error with source context. Sanitizes the error to avoid PII leakage.
 * @param {string} source - Where the error occurred (e.g. 'skill-planner', 'backfill', 'chat')
 * @param {Error|string} error - The error object or message
 * @param {object} [context] - Additional context (skill name, loop name, etc.)
 */
function trackError(source, error, context) {
  const msg = typeof error === 'string' ? error : (error?.message || 'unknown');
  // Sanitize: strip file paths, API keys, and personal data
  const sanitized = msg
    .replace(/\/Users\/[^\s:]+/g, '<path>')
    .replace(/sk-[a-zA-Z0-9_-]{10,}/g, '<key>')
    .replace(/AIza[a-zA-Z0-9_-]{10,}/g, '<key>')
    .slice(0, 300);
  track('error', {
    source,
    message: sanitized,
    stack: (error?.stack || '').split('\n').slice(0, 3).join(' | ').replace(/\/Users\/[^\s:]+/g, '<path>').slice(0, 500),
    ...context,
  });
}

/**
 * Track compat feature usage (called once per boot, not per-request).
 * @param {object} features - { feature_key: true/false, ... }
 */
function trackCompatUsage(features) {
  if (isDisabled()) return;
  track('compat_usage', features);
}

module.exports = { track, trackError, flush, start, stop, isDisabled, getInstallId, getMachineBucket, getTelemetryUser, getVersion, printNoticeIfFirstRun, trackFunnelStep, trackCompatUsage, drainCliLifecycleEvents };
