import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';

import { detectMimeType, isTextFile } from '@dtoolkit/core';
import { Command } from 'commander';
import pc from 'picocolors';

import { executePrompt, streamPrompt } from '../lib/runner.js';
import { readStdin } from '../lib/stdin.js';
import type { InputFile, ProviderName } from '../lib/types.js';

/** Create the `dproxy ask` Commander command with all CLI options. */
export function createAskCommand(): Command {
  return new Command('ask')
    .description('Send a prompt to an AI model')
    .argument('[prompt...]', 'The prompt to send')
    .option('-p, --provider <provider>', 'Provider to use (claude, codex, gemini, opencode)')
    .option('-m, --model <model>', 'Model to use')
    .option('--max-turns <n>', 'Max agent turns', parseInt)
    .option('--max-budget-usd <n>', 'Max budget in USD', parseFloat)
    .option('-o, --output-format <format>', 'Output format: text, json, stream-json')
    .option('--stream', 'Stream response text as it arrives')
    .option('--system-prompt <text>', 'System prompt override')
    .option('--no-memory', 'Skip memory injection')
    .option('--memory <keys>', 'Inject only specific memory keys (comma-separated)')
    .option('--no-life', 'Skip life/PARA context injection')
    .option('-f, --file <paths...>', 'Attach files to the prompt')
    .option('--no-history', "Don't save to history")
    .option('--raw', 'Print raw JSON response')
    .option('--token-footer', 'Append token usage footer to response text')
    .option(
      '--max-session-tokens <n>',
      'Reset session if context exceeds this token count',
      parseInt,
    )
    .option('-c, --continue', 'Continue last conversation')
    .option('-r, --resume <id>', 'Resume a specific session')
    .action(async (promptParts: string[], opts) => {
      try {
        await runAsk(promptParts, opts);
      } catch (err) {
        console.error(pc.red((err as Error).message));
        process.exit(1);
      }
    });
}

/**
 * Execute a single-shot prompt via CLI: read stdin, call the runner,
 * and format output to stdout.
 */
export async function runAsk(promptParts: string[], opts: Record<string, unknown>): Promise<void> {
  const stdinContent = await readStdin();
  const promptText = promptParts.join(' ');

  if (!promptText && !stdinContent) {
    console.error(pc.red('No prompt provided. Usage: dproxy ask "your question"'));
    process.exit(1);
  }

  let fullPrompt = promptText;
  if (stdinContent) {
    fullPrompt = fullPrompt ? `${fullPrompt}\n\n---\n\n${stdinContent}` : stdinContent;
  }

  let files: InputFile[] | undefined;
  if (opts.file) {
    files = await Promise.all(
      (opts.file as string[]).map(async (filePath) => {
        const name = basename(filePath);
        const mimeType = detectMimeType(name);
        const raw = await readFile(filePath);
        const isText = isTextFile({ name, mimeType, data: '' });
        return { name, mimeType, data: isText ? raw.toString('utf-8') : raw.toString('base64') };
      }),
    );
  }

  const memoryOpt =
    opts.memory === false
      ? false
      : typeof opts.memory === 'string'
        ? (opts.memory as string).split(',')
        : undefined;

  const runnerOpts = {
    provider: opts.provider as ProviderName | undefined,
    model: opts.model as string | undefined,
    maxTurns: opts.maxTurns as number | undefined,
    maxBudgetUsd: opts.maxBudgetUsd as number | undefined,
    systemPrompt: opts.systemPrompt as string | undefined,
    memory: memoryOpt,
    life: opts.life !== false ? undefined : false,
    files,
    sessionId: opts.resume as string | undefined,
    continueSession: opts.continue as boolean | undefined,
    maxSessionTokens: opts.maxSessionTokens as number | undefined,
    saveHistory: opts.history !== false,
  };

  if (opts.stream) {
    for await (const event of streamPrompt(fullPrompt, runnerOpts)) {
      if (opts.raw && event.raw) {
        process.stdout.write(JSON.stringify(event.raw) + '\n');
      } else if (opts.raw) {
        process.stdout.write(JSON.stringify(event.result?.raw ?? event) + '\n');
      } else if (event.type === 'text' && event.text) {
        process.stdout.write(event.text);
      } else if (event.type === 'result' && event.result) {
        process.stdout.write('\n');
        if (opts.tokenFooter && event.result.usage) {
          const u = event.result.usage;
          const parts: string[] = [];
          parts.push(`in:${u.inputTokens.toLocaleString()}`);
          parts.push(`out:${u.outputTokens.toLocaleString()}`);
          parts.push(`~${u.totalTokens.toLocaleString()}`);
          process.stdout.write(`\n—————————————\n\`${parts.join(' · ')}\`\n`);
        }
      }
    }
    return;
  }

  const result = await executePrompt(fullPrompt, runnerOpts);

  if (opts.tokenFooter && result.usage) {
    const u = result.usage;
    const parts: string[] = [];
    parts.push(`in:${u.inputTokens.toLocaleString()}`);
    parts.push(`out:${u.outputTokens.toLocaleString()}`);
    parts.push(`~${u.totalTokens.toLocaleString()}`);
    const footer = `\n\n—————————————\n\`${parts.join(' · ')}\``;
    result.text += footer;
  }

  if (opts.raw) {
    console.log(JSON.stringify(result.raw ?? result, null, 2));
  } else if (opts.outputFormat === 'json') {
    console.log(
      JSON.stringify(
        { result: result.text, sessionId: result.sessionId, costUsd: result.costUsd },
        null,
        2,
      ),
    );
  } else {
    console.log(result.text);
  }
}
