/**
 * Chat-style smoke test reporter.
 *
 * Wraps SmokeMcpSession to log every request/response as a formatted
 * "chat" between client and MCP server — like a conversation transcript.
 *
 * Usage:
 *   const session = new SmokeMcpSession('my-project');
 *   const chat = new ChatReporter(session);
 *   chat.section('Gating');
 *   await chat.initialize();
 *   const tools = await chat.listTools();
 *   chat.check('Tool count >= 1', tools.length, (v) => v >= 1);
 */

import type { SmokeMcpSession } from './mcp-client.js';

const COLORS = {
  reset: '\x1b[0m',
  dim: '\x1b[2m',
  bold: '\x1b[1m',
  green: '\x1b[32m',
  red: '\x1b[31m',
  yellow: '\x1b[33m',
  cyan: '\x1b[36m',
  magenta: '\x1b[35m',
  blue: '\x1b[34m',
  gray: '\x1b[90m',
  white: '\x1b[37m',
  bgBlue: '\x1b[44m',
  bgGray: '\x1b[100m',
};

function c(color: keyof typeof COLORS, text: string): string {
  return `${COLORS[color]}${text}${COLORS.reset}`;
}

function truncate(text: string, max: number): string {
  if (text.length <= max) return text;
  return text.slice(0, max - 3) + '...';
}

function elapsed(ms: number): string {
  if (ms < 1000) return `${ms}ms`;
  return `${(ms / 1000).toFixed(1)}s`;
}

type Tool = { name: string; description?: string; inputSchema?: unknown };
type ToolResult = { content: Array<{ type: string; text?: string }>; isError?: boolean };

/** Global pass/fail tracker across all ChatReporter instances in a test run. */
const globalChecks: Array<{ section: string; label: string; passed: boolean; detail?: string }> = [];

export class ChatReporter {
  private currentSection = '';

  constructor(private readonly session: SmokeMcpSession) {}

  // ── Section headers ──

  /** Print a bold section header to separate test phases visually. */
  section(title: string): void {
    this.currentSection = title;
    console.log('');
    console.log(c('bold', `  ━━━ ${title} ━━━`));
  }

  /** Print an informational note. */
  info(msg: string): void {
    console.log(`  ${c('dim', `ℹ  ${msg}`)}`);
  }

  /** Print a skip message. */
  skip(msg: string): void {
    console.log(`  ${c('yellow', `⏭  ${msg}`)}`);
  }

  // ── MCP operations with logging ──

  async initialize(): Promise<unknown> {
    this.log('client', 'initialize');
    const t0 = Date.now();
    try {
      const result = await this.session.initialize();
      const res = result as { serverInfo?: { name?: string }; protocolVersion?: string };
      this.log('server', `initialized ${c('dim', `(${res.serverInfo?.name ?? '?'}, ${elapsed(Date.now() - t0)})`)}`);
      return result;
    } catch (err) {
      this.log('error', `initialize failed: ${err instanceof Error ? err.message : err}`);
      throw err;
    }
  }

  async sendNotification(method: string, params: Record<string, unknown> = {}): Promise<void> {
    this.log('client', `${method} ${c('dim', '(notification)')}`);
    await this.session.sendNotification(method, params);
  }

  async listTools(): Promise<Tool[]> {
    this.log('client', 'tools/list');
    const t0 = Date.now();
    try {
      const tools = await this.session.listTools();
      const names = tools.map((t) => t.name);
      this.log('server', `tools: ${c('bold', names.join(', '))} ${c('dim', `(${tools.length} tool${tools.length !== 1 ? 's' : ''}, ${elapsed(Date.now() - t0)})`)}`);
      return tools;
    } catch (err) {
      this.log('error', `tools/list failed: ${err instanceof Error ? err.message : err}`);
      throw err;
    }
  }

  async callTool(name: string, args: Record<string, unknown> = {}, timeout?: number): Promise<ToolResult> {
    const argStr = Object.keys(args).length > 0
      ? ' ' + c('dim', JSON.stringify(args).slice(0, 80))
      : '';
    this.log('client', `call ${c('bold', name)}${argStr}`);
    const t0 = Date.now();
    try {
      const result = await this.session.callTool(name, args, timeout);
      const text = result.content?.[0]?.text ?? '';
      const isErr = result.isError;
      if (isErr) {
        this.log('server', `${c('red', '✗')} ${truncate(text.replace(/\n/g, ' '), 120)} ${c('dim', `(${elapsed(Date.now() - t0)})`)}`);
      } else {
        const preview = truncate(text.replace(/\n/g, ' '), 100);
        this.log('server', `${c('green', '✓')} ${c('dim', preview)} ${c('gray', `(${text.length} chars, ${elapsed(Date.now() - t0)})`)}`);
      }
      return result;
    } catch (err) {
      this.log('error', `call ${name} failed: ${err instanceof Error ? err.message : err} ${c('dim', `(${elapsed(Date.now() - t0)})`)}`);
      throw err;
    }
  }

  async close(): Promise<void> {
    await this.session.close();
  }

  // ── Assertions ──

  /**
   * Log an assertion result inline. Returns the boolean result.
   * @deprecated Use check() instead — same thing, shorter name.
   */
  expectAndLog<T>(label: string, actual: T, matcher: (v: T) => boolean): boolean {
    return this.check(label, actual, matcher);
  }

  /** Log a pass/fail check inline. */
  check<T>(label: string, actual: T, matcher: (v: T) => boolean): boolean {
    const passed = matcher(actual);
    const detail = typeof actual === 'string'
      ? truncate(actual, 60)
      : typeof actual === 'number' || typeof actual === 'boolean'
        ? String(actual)
        : truncate(JSON.stringify(actual), 60);

    const icon = passed ? c('green', '✓') : c('red', '✗');
    console.log(`    ${icon} ${label} ${c('dim', `→ ${detail}`)}`);
    globalChecks.push({ section: this.currentSection, label, passed, detail });
    return passed;
  }

  /** Print a final summary of all checks across all reporters. */
  static printSummary(): void {
    const passed = globalChecks.filter((a) => a.passed).length;
    const failed = globalChecks.filter((a) => !a.passed).length;
    const total = globalChecks.length;

    console.log('');
    console.log(c('bold', '  ━━━ Summary ━━━'));
    if (failed === 0) {
      console.log(`  ${c('green', `✓ All ${total} checks passed`)}`);
    } else {
      console.log(`  ${c('red', `✗ ${failed}/${total} checks failed:`)}`);
      for (const a of globalChecks.filter((a) => !a.passed)) {
        const sec = a.section ? `[${a.section}] ` : '';
        console.log(`    ${c('red', '✗')} ${sec}${a.label} ${c('dim', `→ ${a.detail ?? '?'}`)}`);
      }
    }
    console.log('');
  }

  // ── Internal ──

  private log(direction: 'client' | 'server' | 'error', message: string): void {
    if (direction === 'client') {
      console.log(`  ${c('cyan', '→')} ${message}`);
    } else if (direction === 'server') {
      console.log(`  ${c('magenta', '←')} ${message}`);
    } else {
      console.log(`  ${c('red', '✗')} ${message}`);
    }
  }
}
