import { spawn } from 'node:child_process';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import type { LlmProvider, CompletionOptions, CompletionResult } from './types.js';

const execFileAsync = promisify(execFile);

export interface GeminiCliConfig {
  binaryPath?: string;
  defaultModel?: string;
  timeoutMs?: number;
}

/**
 * Gemini CLI provider. Spawns the `gemini` binary in non-interactive mode
 * using the -p (--prompt) flag and captures stdout.
 *
 * Note: This provider does not support tool calls since the CLI interface
 * only returns text output. toolCalls will always be an empty array.
 */
export class GeminiCliProvider implements LlmProvider {
  readonly name = 'gemini-cli';
  private binaryPath: string;
  private defaultModel: string;
  private timeoutMs: number;

  constructor(config?: GeminiCliConfig) {
    this.binaryPath = config?.binaryPath ?? 'gemini';
    this.defaultModel = config?.defaultModel ?? 'gemini-2.5-flash';
    this.timeoutMs = config?.timeoutMs ?? 30000;
  }

  async complete(options: CompletionOptions): Promise<CompletionResult> {
    const model = options.model ?? this.defaultModel;

    // Build prompt from messages
    const prompt = options.messages
      .map((m) => {
        if (m.role === 'system') return `System: ${m.content}`;
        if (m.role === 'user') return m.content;
        if (m.role === 'assistant') return `Assistant: ${m.content}`;
        return m.content;
      })
      .join('\n\n');

    const args = ['-p', prompt, '-m', model, '-o', 'text'];

    const content = await this.spawn(args);

    return {
      content: content.trim(),
      toolCalls: [],
      usage: {
        promptTokens: 0,
        completionTokens: 0,
        totalTokens: 0,
      },
      finishReason: 'stop',
    };
  }

  async listModels(): Promise<string[]> {
    // The Gemini CLI does not expose a model listing command;
    // return well-known models.
    return [
      'gemini-2.5-flash',
      'gemini-2.5-pro',
      'gemini-2.0-flash',
    ];
  }

  async isAvailable(): Promise<boolean> {
    try {
      await execFileAsync(this.binaryPath, ['--version'], { timeout: 5000 });
      return true;
    } catch {
      return false;
    }
  }

  private spawn(args: string[]): Promise<string> {
    return new Promise((resolve, reject) => {
      const child = spawn(this.binaryPath, args, {
        stdio: ['ignore', 'pipe', 'pipe'],
        timeout: this.timeoutMs,
      });

      const stdoutChunks: Buffer[] = [];
      const stderrChunks: Buffer[] = [];

      child.stdout.on('data', (chunk: Buffer) => stdoutChunks.push(chunk));
      child.stderr.on('data', (chunk: Buffer) => stderrChunks.push(chunk));

      child.on('error', (err) => {
        if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
          reject(new Error(`Gemini CLI binary not found at '${this.binaryPath}'. Install with: npm install -g @google/gemini-cli`));
        } else {
          reject(err);
        }
      });

      child.on('close', (code) => {
        const stdout = Buffer.concat(stdoutChunks).toString('utf-8');
        if (code === 0) {
          resolve(stdout);
        } else {
          const stderr = Buffer.concat(stderrChunks).toString('utf-8');
          reject(new Error(`Gemini CLI exited with code ${code}: ${stderr.slice(0, 500)}`));
        }
      });
    });
  }
}
