// Tests for per-tool telemetry system

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

// Set up mock home before requiring telemetry
const mockHome = path.join(os.tmpdir(), `pre-test-home-${process.pid}`);
jest.spyOn(os, 'homedir').mockReturnValue(mockHome);

const LOG_FILE = path.join(mockHome, '.pre', 'logs', 'tool_calls.jsonl');
const LOG_DIR = path.dirname(LOG_FILE);

// Now require telemetry (it resolves paths lazily via os.homedir())
const telemetry = require('../src/telemetry');

beforeAll(() => {
  fs.mkdirSync(path.join(mockHome, '.pre'), { recursive: true });
});

afterAll(() => {
  fs.rmSync(mockHome, { recursive: true, force: true });
});

beforeEach(() => {
  try { fs.unlinkSync(LOG_FILE); } catch {}
});

describe('Telemetry — Record', () => {
  test('writes a JSONL entry with privacy-preserving fields', () => {
    telemetry.record({
      session: 'test-session',
      tool: 'bash',
      args: { command: 'ls -la' },
      outcome: 'ok',
      latency_ms: 42,
      result: 'file1.txt\nfile2.txt',
      tier: 'standard',
    });

    const content = fs.readFileSync(LOG_FILE, 'utf-8').trim();
    const entry = JSON.parse(content);
    expect(entry.tool).toBe('bash');
    expect(entry.session).toBe('test-session');
    expect(entry.outcome).toBe('ok');
    expect(entry.latency_ms).toBe(42);
    expect(entry.result_bytes).toBeGreaterThan(0);
    expect(entry.tier).toBe('standard');
    expect(entry.args_hash).toMatch(/^sha256:/);
    expect(entry.ts).toBeGreaterThan(0);
    // Privacy: no raw args or results in the log
    expect(content).not.toContain('ls -la');
    expect(content).not.toContain('file1.txt');
  });

  test('handles missing optional fields', () => {
    telemetry.record({ tool: 'web_search', outcome: 'ok', latency_ms: 100 });

    const entry = JSON.parse(fs.readFileSync(LOG_FILE, 'utf-8').trim());
    expect(entry.tool).toBe('web_search');
    expect(entry.session).toBeNull();
    expect(entry.args_hash).toBeNull();
    expect(entry.tier).toBeNull();
    expect(entry.error_class).toBeNull();
    expect(entry.result_bytes).toBe(0);
  });

  test('records errors with error_class', () => {
    telemetry.record({
      tool: 'github',
      outcome: 'error',
      latency_ms: 500,
      error_class: 'AuthenticationError',
      result: 'Error: Bad credentials',
    });

    const entry = JSON.parse(fs.readFileSync(LOG_FILE, 'utf-8').trim());
    expect(entry.outcome).toBe('error');
    expect(entry.error_class).toBe('AuthenticationError');
  });

  test('appends multiple entries', () => {
    telemetry.record({ tool: 'bash', outcome: 'ok', latency_ms: 10 });
    telemetry.record({ tool: 'read_file', outcome: 'ok', latency_ms: 5 });
    telemetry.record({ tool: 'web_search', outcome: 'error', latency_ms: 200 });

    const lines = fs.readFileSync(LOG_FILE, 'utf-8').trim().split('\n');
    expect(lines.length).toBe(3);
    expect(JSON.parse(lines[0]).tool).toBe('bash');
    expect(JSON.parse(lines[2]).tool).toBe('web_search');
  });
});

describe('Telemetry — Stats', () => {
  test('computes top and bottom tools', () => {
    telemetry.record({ tool: 'bash', outcome: 'ok', latency_ms: 10 });
    telemetry.record({ tool: 'bash', outcome: 'ok', latency_ms: 20 });
    telemetry.record({ tool: 'bash', outcome: 'ok', latency_ms: 15 });
    telemetry.record({ tool: 'read_file', outcome: 'ok', latency_ms: 5 });
    telemetry.record({ tool: 'read_file', outcome: 'ok', latency_ms: 8 });
    telemetry.record({ tool: 'web_search', outcome: 'ok', latency_ms: 200 });

    const result = telemetry.stats();
    expect(result.total_calls).toBe(6);
    expect(result.unique_tools).toBe(3);
    expect(result.top10[0].name).toBe('bash');
    expect(result.top10[0].calls).toBe(3);
    expect(result.top10[0].avg_latency_ms).toBe(15);
  });

  test('identifies broken tools (>20% error rate)', () => {
    telemetry.record({ tool: 'smartsheet', outcome: 'ok', latency_ms: 100 });
    telemetry.record({ tool: 'smartsheet', outcome: 'error', latency_ms: 50, error_class: 'TokenExpired' });
    telemetry.record({ tool: 'smartsheet', outcome: 'error', latency_ms: 60, error_class: 'TokenExpired' });
    telemetry.record({ tool: 'bash', outcome: 'ok', latency_ms: 10 });
    telemetry.record({ tool: 'bash', outcome: 'ok', latency_ms: 10 });
    telemetry.record({ tool: 'bash', outcome: 'ok', latency_ms: 10 });

    const result = telemetry.stats();
    expect(result.broken.length).toBe(1);
    expect(result.broken[0].name).toBe('smartsheet');
    expect(result.broken[0].error_rate).toBeCloseTo(0.667, 2);
  });

  test('identifies repair-heavy tools (>10% repair rate)', () => {
    telemetry.record({ tool: 'github', outcome: 'ok', latency_ms: 100 });
    telemetry.record({ tool: 'github', outcome: 'ok', latency_ms: 100 });
    telemetry.record({ tool: 'github', outcome: 'repair', latency_ms: 200 });
    telemetry.record({ tool: 'github', outcome: 'repair', latency_ms: 200 });

    const result = telemetry.stats();
    expect(result.repair_heavy.length).toBe(1);
    expect(result.repair_heavy[0].name).toBe('github');
    expect(result.repair_heavy[0].repair_rate).toBe(0.5);
  });

  test('tool drilldown shows daily breakdown and error classes', () => {
    telemetry.record({ tool: 'bash', outcome: 'ok', latency_ms: 10 });
    telemetry.record({ tool: 'bash', outcome: 'error', latency_ms: 50, error_class: 'ENOENT' });
    telemetry.record({ tool: 'bash', outcome: 'ok', latency_ms: 20 });

    const result = telemetry.stats({ tool: 'bash' });
    expect(result.tool).toBe('bash');
    expect(result.calls).toBe(3);
    expect(result.outcomes.ok).toBe(2);
    expect(result.outcomes.error).toBe(1);
    expect(result.error_rate).toBeCloseTo(0.333, 2);
    expect(result.error_classes.ENOENT).toBe(1);
    expect(Object.keys(result.daily_calls).length).toBeGreaterThan(0);
  });

  test('returns empty for unknown tool', () => {
    const result = telemetry.stats({ tool: 'nonexistent' });
    expect(result.calls).toBe(0);
  });

  test('filters by days', () => {
    // Write entries directly with old timestamps
    fs.mkdirSync(LOG_DIR, { recursive: true });
    const oldTs = Date.now() - (40 * 24 * 60 * 60 * 1000); // 40 days ago
    const recentTs = Date.now();
    const lines = [
      JSON.stringify({ ts: oldTs, tool: 'old_tool', outcome: 'ok', latency_ms: 10 }),
      JSON.stringify({ ts: recentTs, tool: 'new_tool', outcome: 'ok', latency_ms: 20 }),
    ];
    fs.writeFileSync(LOG_FILE, lines.join('\n') + '\n');

    const result7d = telemetry.stats({ days: 7 });
    expect(result7d.total_calls).toBe(1);
    expect(result7d.all_tools[0].name).toBe('new_tool');

    const resultAll = telemetry.stats();
    expect(resultAll.total_calls).toBe(2);
  });
});

describe('Telemetry — Rotation', () => {
  test('removes entries older than 30 days', () => {
    fs.mkdirSync(LOG_DIR, { recursive: true });
    const oldTs = Date.now() - (31 * 24 * 60 * 60 * 1000);
    const recentTs = Date.now();
    const lines = [
      JSON.stringify({ ts: oldTs, tool: 'old', outcome: 'ok', latency_ms: 10 }),
      JSON.stringify({ ts: recentTs, tool: 'recent', outcome: 'ok', latency_ms: 20 }),
    ];
    fs.writeFileSync(LOG_FILE, lines.join('\n') + '\n');

    telemetry.rotate();

    const remaining = fs.readFileSync(LOG_FILE, 'utf-8').trim().split('\n');
    expect(remaining.length).toBe(1);
    expect(JSON.parse(remaining[0]).tool).toBe('recent');
  });

  test('handles missing log file gracefully', () => {
    expect(() => telemetry.rotate()).not.toThrow();
  });
});

describe('Telemetry — Shutdown', () => {
  test('shutdown is idempotent', () => {
    expect(() => {
      telemetry.shutdown();
      telemetry.shutdown();
    }).not.toThrow();
  });

  test('can write after shutdown', () => {
    telemetry.shutdown();
    telemetry.record({ tool: 'bash', outcome: 'ok', latency_ms: 10 });
    const content = fs.readFileSync(LOG_FILE, 'utf-8').trim();
    expect(JSON.parse(content).tool).toBe('bash');
  });
});
