/**
 * v0.41.16.0 — `gbrain conversation-parser` debug CLI tests.
 *
 * Pins:
 *   - list-builtins prints all 12 patterns + accepts --json
 *   - validate emits "deferred to v0.42+" notice (v1 has no compiler)
 *   - scan errors without engine
 *   - --help works
 *
 * Pure-function tests; no engine, no DB.
 */

import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
import { mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { runConversationParser } from '../src/commands/conversation-parser.ts';
import { BUILTIN_PATTERNS } from '../src/core/conversation-parser/builtins.ts';

// Capture process.stdout.write + process.stderr.write + process.exit.
function captureStdio() {
  const out: string[] = [];
  const err: string[] = [];
  let exitCode: number | null = null;
  const origOut = process.stdout.write.bind(process.stdout);
  const origErr = process.stderr.write.bind(process.stderr);
  const origExit = process.exit.bind(process);
  process.stdout.write = ((chunk: string | Uint8Array) => {
    out.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString());
    return true;
  }) as typeof process.stdout.write;
  process.stderr.write = ((chunk: string | Uint8Array) => {
    err.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString());
    return true;
  }) as typeof process.stderr.write;
  // process.exit throws to short-circuit; tests catch and inspect.
  process.exit = ((code?: number) => {
    exitCode = code ?? 0;
    throw new Error(`PROCESS_EXIT:${exitCode}`);
  }) as typeof process.exit;
  return {
    out,
    err,
    getExitCode: () => exitCode,
    restore: () => {
      process.stdout.write = origOut;
      process.stderr.write = origErr;
      process.exit = origExit;
    },
  };
}

describe('runConversationParser — help', () => {
  test('--help prints usage', async () => {
    const cap = captureStdio();
    try {
      await runConversationParser(null, ['--help']);
    } finally {
      cap.restore();
    }
    const text = cap.out.join('');
    expect(text).toContain('Usage: gbrain conversation-parser');
    expect(text).toContain('scan');
    expect(text).toContain('list-builtins');
    expect(text).toContain('validate');
  });

  test('no subcommand prints help', async () => {
    const cap = captureStdio();
    try {
      await runConversationParser(null, []);
    } finally {
      cap.restore();
    }
    expect(cap.out.join('')).toContain('Usage: gbrain conversation-parser');
  });
});

describe('runConversationParser — list-builtins', () => {
  test('human output includes all built-in pattern ids', async () => {
    const cap = captureStdio();
    try {
      await runConversationParser(null, ['list-builtins']);
    } finally {
      cap.restore();
    }
    const text = cap.out.join('');
    for (const pattern of BUILTIN_PATTERNS) {
      expect(text).toContain(pattern.id);
    }
    expect(text).toContain(`${BUILTIN_PATTERNS.length} built-in patterns`);
  });

  test('--json output is stable schema', async () => {
    const cap = captureStdio();
    try {
      await runConversationParser(null, ['list-builtins', '--json']);
    } finally {
      cap.restore();
    }
    const json = JSON.parse(cap.out.join('').trim());
    expect(json.schema_version).toBe(1);
    expect(json.total).toBe(BUILTIN_PATTERNS.length);
    expect(json.patterns).toHaveLength(BUILTIN_PATTERNS.length);
    expect(json.patterns[0].id).toBe('imessage-slack');
    expect(json.patterns[0].date_source).toBeDefined();
    expect(json.patterns[0].time_format).toBeDefined();
    expect(json.patterns[0].timezone_policy).toBeDefined();
    expect(json.patterns[0].regex).toBeDefined();
  });
});

describe('runConversationParser — validate', () => {
  test('emits deferred notice (v0.42+ scope)', async () => {
    const dir = mkdtempSync(join(tmpdir(), 'cv-parser-cli-'));
    const path = join(dir, 'pattern.json');
    writeFileSync(path, '{}');
    const cap = captureStdio();
    try {
      await runConversationParser(null, ['validate', path]);
    } finally {
      cap.restore();
    }
    expect(cap.out.join('')).toContain('deferred to v0.42+');
  });

  test('--json validate emits structured deferred envelope', async () => {
    const dir = mkdtempSync(join(tmpdir(), 'cv-parser-cli-'));
    const path = join(dir, 'pattern.json');
    writeFileSync(path, '{}');
    const cap = captureStdio();
    try {
      await runConversationParser(null, ['validate', path, '--json']);
    } finally {
      cap.restore();
    }
    const json = JSON.parse(cap.out.join('').trim());
    expect(json.schema_version).toBe(1);
    expect(json.status).toBe('deferred');
    expect(json.todo_ref).toContain('v0.41.16.0');
  });

  test('exits 2 on missing file path', async () => {
    const cap = captureStdio();
    try {
      await runConversationParser(null, ['validate']);
    } catch (e) {
      expect((e as Error).message).toBe('PROCESS_EXIT:2');
    } finally {
      cap.restore();
    }
    expect(cap.getExitCode()).toBe(2);
  });

  test('exits 2 on nonexistent file', async () => {
    const cap = captureStdio();
    try {
      await runConversationParser(null, [
        'validate',
        '/nonexistent/path.json',
      ]);
    } catch (e) {
      expect((e as Error).message).toBe('PROCESS_EXIT:2');
    } finally {
      cap.restore();
    }
    expect(cap.getExitCode()).toBe(2);
  });
});

describe('runConversationParser — scan', () => {
  test('exits 2 when engine is null (scan requires brain)', async () => {
    const cap = captureStdio();
    try {
      await runConversationParser(null, ['scan', 'some-slug']);
    } catch (e) {
      expect((e as Error).message).toBe('PROCESS_EXIT:2');
    } finally {
      cap.restore();
    }
    expect(cap.getExitCode()).toBe(2);
  });
});

describe('runConversationParser — unknown subcommand', () => {
  test('exits 2 with hint', async () => {
    const cap = captureStdio();
    try {
      await runConversationParser(null, ['bogus-cmd']);
    } catch (e) {
      expect((e as Error).message).toBe('PROCESS_EXIT:2');
    } finally {
      cap.restore();
    }
    expect(cap.err.join('')).toContain('unknown subcommand');
  });
});
