'use strict';
/**
 * Tests for scripts/lib/llm-summary.js
 *
 * Run with: node tests/lib/llm-summary.test.js
 */

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

const { extractConversationText, getContextRemainingPct, getContextThreshold, getLLMModel, generateSessionSummary } = require('../../scripts/lib/llm-summary');

console.log('=== Testing llm-summary.js ===\n');

let passed = 0;
let failed = 0;

function test(desc, fn) {
  try {
    fn();
    console.log(`  ✓ ${desc}`);
    passed++;
  } catch (e) {
    console.log(`  ✗ ${desc}: ${e.message}`);
    failed++;
  }
}

let seq = 0;
const transcriptDir = fs.mkdtempSync(path.join(os.tmpdir(), 'llm-summary-test-'));
function writeTranscript(lines) {
  seq++;
  const p = path.join(transcriptDir, `transcript-${seq}.jsonl`);
  fs.writeFileSync(p, lines.join('\n') + '\n');
  return p;
}

function userEntry(text) {
  return JSON.stringify({ type: 'user', message: { role: 'user', content: [{ type: 'text', text }] } });
}

function assistantEntry(text) {
  return JSON.stringify({
    type: 'assistant',
    message: {
      role: 'assistant',
      content: [{ type: 'text', text }],
      usage: { input_tokens: 1000, cache_read_input_tokens: 0, cache_creation_input_tokens: 0 }
    }
  });
}

// --- getLLMModel ---
console.log('getLLMModel:');

test('returns haiku by default', () => {
  const orig = process.env.ECC_LLM_SUMMARY_MODEL;
  delete process.env.ECC_LLM_SUMMARY_MODEL;
  assert.strictEqual(getLLMModel(), 'haiku');
  if (orig !== undefined) process.env.ECC_LLM_SUMMARY_MODEL = orig;
});

test('reads ECC_LLM_SUMMARY_MODEL env var', () => {
  const orig = process.env.ECC_LLM_SUMMARY_MODEL;
  process.env.ECC_LLM_SUMMARY_MODEL = 'sonnet';
  assert.strictEqual(getLLMModel(), 'sonnet');
  if (orig !== undefined) process.env.ECC_LLM_SUMMARY_MODEL = orig;
  else delete process.env.ECC_LLM_SUMMARY_MODEL;
});

// --- getContextThreshold ---
console.log('\ngetContextThreshold:');

test('returns 20 by default', () => {
  const orig = process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD;
  delete process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD;
  assert.strictEqual(getContextThreshold(), 20);
  if (orig !== undefined) process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD = orig;
});

test('reads ECC_LLM_SUMMARY_CONTEXT_THRESHOLD env var', () => {
  const orig = process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD;
  process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD = '70';
  assert.strictEqual(getContextThreshold(), 70);
  if (orig !== undefined) process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD = orig;
  else delete process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD;
});

test('falls back to 20 on invalid value', () => {
  const orig = process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD;
  process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD = 'notanumber';
  assert.strictEqual(getContextThreshold(), 20);
  if (orig !== undefined) process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD = orig;
  else delete process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD;
});

test('falls back to 20 when value exceeds 100', () => {
  const orig = process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD;
  process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD = '150';
  assert.strictEqual(getContextThreshold(), 20);
  if (orig !== undefined) process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD = orig;
  else delete process.env.ECC_LLM_SUMMARY_CONTEXT_THRESHOLD;
});

// --- extractConversationText ---
console.log('\nextractConversationText:');

test('returns null for missing file', () => {
  assert.strictEqual(extractConversationText('/nonexistent/path.jsonl'), null);
});

test('returns null for empty transcript', () => {
  const p = writeTranscript([]);
  assert.strictEqual(extractConversationText(p), null);
});

test('extracts user and assistant turns', () => {
  const p = writeTranscript([userEntry('Hello, can you help?'), assistantEntry('Sure, what do you need?')]);
  const result = extractConversationText(p);
  assert.ok(result.includes('User:'));
  assert.ok(result.includes('Claude:'));
  assert.ok(result.includes('Hello, can you help?'));
});

test('truncates user text to 400 chars', () => {
  const p = writeTranscript([userEntry('x'.repeat(500))]);
  const result = extractConversationText(p);
  assert.ok(result !== null);
  assert.ok(!result.includes('x'.repeat(401)));
});

test('skips unparseable lines gracefully', () => {
  const p = writeTranscript(['not valid json', userEntry('valid message')]);
  const result = extractConversationText(p);
  assert.ok(result !== null);
  assert.ok(result.includes('valid message'));
});

test('limits to last 25 turns', () => {
  const lines = [];
  for (let i = 0; i < 30; i++) lines.push(userEntry(`message ${i}`));
  const p = writeTranscript(lines);
  const result = extractConversationText(p);
  assert.ok(result.includes('message 29'));
  assert.ok(!result.includes('message 4'));
});

test('collapses newlines to spaces', () => {
  const p = writeTranscript([userEntry('line one\nline two')]);
  const result = extractConversationText(p);
  assert.ok(!result.includes('\nline two'));
  assert.ok(result.includes('line one line two'));
});

// --- getContextRemainingPct ---
console.log('\ngetContextRemainingPct:');

test('returns null for missing file', () => {
  assert.strictEqual(getContextRemainingPct('/nonexistent.jsonl'), null);
});

test('returns null for transcript with no usage data', () => {
  const p = writeTranscript([userEntry('hi')]);
  assert.strictEqual(getContextRemainingPct(p), null);
});

test('returns numeric percentage for transcript with usage data', () => {
  const p = writeTranscript([assistantEntry('ok')]);
  const pct = getContextRemainingPct(p);
  assert.ok(typeof pct === 'number');
  assert.ok(pct >= 0 && pct <= 100);
});

// --- generateSessionSummary ---
console.log('\ngenerateSessionSummary:');

test('returns null when ECC_SKIP_LLM_SUMMARY is set', () => {
  const orig = process.env.ECC_SKIP_LLM_SUMMARY;
  process.env.ECC_SKIP_LLM_SUMMARY = '1';
  const p = writeTranscript([userEntry('test')]);
  assert.strictEqual(generateSessionSummary(p), null);
  if (orig !== undefined) process.env.ECC_SKIP_LLM_SUMMARY = orig;
  else delete process.env.ECC_SKIP_LLM_SUMMARY;
});

test('returns null for missing transcript (no conversation to summarize)', () => {
  const orig = process.env.ECC_SKIP_LLM_SUMMARY;
  delete process.env.ECC_SKIP_LLM_SUMMARY;
  assert.strictEqual(generateSessionSummary('/nonexistent.jsonl'), null);
  if (orig !== undefined) process.env.ECC_SKIP_LLM_SUMMARY = orig;
});

// --- Results ---
console.log('\n=== Test Results ===');
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
console.log(`Total:  ${passed + failed}`);
process.exit(failed > 0 ? 1 : 0);
