/**
 * Wiki Command
 *
 * Generates repository documentation from the knowledge graph.
 * Usage: ontoindex wiki [path] [options]
 */

import path from 'path';
import readline from 'readline';
import { execFileSync } from 'child_process';
import cliProgress from 'cli-progress';
import { getGitRoot, isGitRepo } from '../storage/git.js';
import {
  getStoragePaths,
  loadMeta,
  loadCLIConfig,
  saveCLIConfig,
} from '../storage/repo-manager.js';
import { WikiGenerator, type WikiOptions } from '../core/wiki/generator.js';
import { resolveLLMConfig, type LLMProvider } from '../core/wiki/llm-client.js';
import { detectCursorCLI } from '../core/wiki/cursor-client.js';

interface WikiCommandOptions {
  force?: boolean;
  model?: string;
  baseUrl?: string;
  apiKey?: string;
  apiVersion?: string;
  reasoningModel?: boolean;
  concurrency?: string;
  gist?: boolean;
  provider?: LLMProvider;
  verbose?: boolean;
  review?: boolean;
  maxTokens?: string;
}

/**
 * Prompt the user for input via stdin.
 */
function prompt(question: string, hide = false): Promise<string> {
  return new Promise((resolve) => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });

    if (hide && process.stdin.isTTY) {
      // Mask input for API keys
      process.stdout.write(question);
      let input = '';
      process.stdin.setRawMode(true);
      process.stdin.resume();
      process.stdin.setEncoding('utf-8');

      const onData = (char: string) => {
        if (char === '\n' || char === '\r' || char === '\u0004') {
          process.stdin.setRawMode(false);
          process.stdin.removeListener('data', onData);
          process.stdout.write('\n');
          rl.close();
          resolve(input);
        } else if (char === '\u0003') {
          // Ctrl+C
          process.stdin.setRawMode(false);
          rl.close();
          process.exit(1);
        } else if (char === '\u007F' || char === '\b') {
          // Backspace
          if (input.length > 0) {
            input = input.slice(0, -1);
            process.stdout.write('\b \b');
          }
        } else {
          input += char;
          process.stdout.write('*');
        }
      };
      process.stdin.on('data', onData);
    } else {
      rl.question(question, (answer) => {
        rl.close();
        resolve(answer.trim());
      });
    }
  });
}

function errorMessage(err: unknown): string | undefined {
  if (err instanceof Error) return err.message;
  if (typeof err !== 'object' || err === null) return undefined;

  const message = (err as { message?: unknown }).message;
  return message ? String(message) : undefined;
}

export const wikiCommand = async (inputPath?: string, options?: WikiCommandOptions) => {
  console.warn(
    '\n  DEPRECATION WARNING: `ontoindex wiki` is deprecated.\n' +
      '  Use `ontoindex audit [--annotate]` for structured audit reports.\n' +
      '  For architecture exploration:\n' +
      '    READ ontoindex://repo/{name}/clusters\n' +
      '    READ ontoindex://repo/{name}/context\n',
  );

  // Set verbose mode globally for cursor-client to pick up
  if (options?.verbose) {
    process.env.ONTOINDEX_VERBOSE = '1';
  }

  console.log('\n  OntoIndex Wiki Generator\n');

  // ── Resolve repo path ───────────────────────────────────────────────
  let repoPath: string;
  if (inputPath) {
    repoPath = path.resolve(inputPath);
  } else {
    const gitRoot = getGitRoot(process.cwd());
    if (!gitRoot) {
      console.log('  Error: Not inside a git repository\n');
      process.exitCode = 1;
      return;
    }
    repoPath = gitRoot;
  }

  if (!isGitRepo(repoPath)) {
    console.log('  Error: Not a git repository\n');
    process.exitCode = 1;
    return;
  }

  // ── Check for existing index ────────────────────────────────────────
  const { storagePath, lbugPath } = getStoragePaths(repoPath);
  const meta = await loadMeta(storagePath);

  if (!meta) {
    console.log('  Error: No OntoIndex index found.');
    console.log('  Run `ontoindex analyze` first to index this repository.\n');
    process.exitCode = 1;
    return;
  }

  // ── Resolve LLM config (with interactive fallback) ─────────────────
  // Save any CLI overrides immediately
  if (
    options?.apiKey ||
    options?.model ||
    options?.baseUrl ||
    options?.provider ||
    options?.apiVersion ||
    options?.reasoningModel !== undefined
  ) {
    const existing = await loadCLIConfig();
    const updates: Partial<typeof existing> = {};
    if (options.apiKey) updates.apiKey = options.apiKey;
    if (options.baseUrl) updates.baseUrl = options.baseUrl;
    if (options.provider) updates.provider = options.provider;
    if (options.apiVersion) updates.apiVersion = options.apiVersion;
    if (options.reasoningModel !== undefined) updates.isReasoningModel = options.reasoningModel;
    // Save model to appropriate field based on provider
    if (options.model) {
      if (options.provider === 'cursor') {
        updates.cursorModel = options.model;
      } else {
        updates.model = options.model;
      }
    }
    await saveCLIConfig({ ...existing, ...updates });
    console.log('  Config saved to ~/.ontoindex/config.json\n');
  }

  const savedConfig = await loadCLIConfig();
  const hasSavedConfig = !!(
    savedConfig.provider === 'cursor' ||
    (savedConfig.apiKey && savedConfig.baseUrl)
  );
  const hasCLIOverrides = !!(
    options?.apiKey ||
    options?.model ||
    options?.baseUrl ||
    options?.provider ||
    options?.apiVersion ||
    options?.reasoningModel !== undefined
  );

  let llmConfig = await resolveLLMConfig({
    model: options?.model,
    baseUrl: options?.baseUrl,
    apiKey: options?.apiKey,
    provider: options?.provider,
    apiVersion: options?.apiVersion,
    isReasoningModel: options?.reasoningModel,
  });

  // Run interactive setup if no saved config and no CLI flags provided
  // (even if env vars exist — let user explicitly choose their provider)
  if (!hasSavedConfig && !hasCLIOverrides) {
    if (!process.stdin.isTTY) {
      // Non-interactive mode — need either API key or Cursor CLI
      if (!llmConfig.apiKey && llmConfig.provider !== 'cursor') {
        console.log('  Error: No LLM API key found.');
        console.log('  Set OPENAI_API_KEY or ONTOINDEX_API_KEY environment variable,');
        console.log('  or pass --api-key <key>, or use --provider cursor.\n');
        process.exitCode = 1;
        return;
      }
      // Non-interactive with env var or cursor — just use it
    } else {
      console.log("  No LLM configured. Let's set it up.\n");
      console.log(
        '  Supports OpenAI, OpenRouter, Azure, any OpenAI-compatible API, or Cursor CLI.\n',
      );

      // Check if Cursor CLI is available
      const hasCursor = detectCursorCLI();

      // Provider selection
      console.log('  [1] OpenAI (api.openai.com)');
      console.log('  [2] OpenRouter (openrouter.ai)');
      console.log('  [3] Azure OpenAI');
      console.log('  [4] Custom endpoint');
      if (hasCursor) {
        console.log('  [5] Cursor CLI (local, uses your Cursor subscription)');
      }
      console.log('');

      const maxChoice = hasCursor ? '5' : '4';
      const choice = await prompt(`  Select provider (1/${maxChoice}): `);

      let baseUrl: string;
      let defaultModel: string;
      let provider: LLMProvider = 'openai';
      let key = '';

      if (choice === '5' && hasCursor) {
        // Cursor CLI selected - model defaults to 'auto' (Cursor's default)
        provider = 'cursor';
        baseUrl = '';

        const modelInput = await prompt('  Model (leave empty for auto): ');
        const model = modelInput || '';

        // Save config for Cursor
        const cursorConfig: Record<string, string> = { provider: 'cursor' };
        if (model) cursorConfig.cursorModel = model;
        await saveCLIConfig(cursorConfig);
        console.log('  Config saved to ~/.ontoindex/config.json\n');

        llmConfig = { ...llmConfig, provider: 'cursor', model, apiKey: '', baseUrl: '' };
      } else if (choice === '3') {
        // Azure OpenAI guided setup — minimal prompts
        console.log('\n  Azure OpenAI setup.\n');

        const endpoint = (
          await prompt('  Endpoint URL (e.g. https://my-resource.openai.azure.com): ')
        )
          .trim()
          .replace(/\/+$/, '');
        if (!endpoint) {
          console.log('\n  No endpoint provided. Aborting.\n');
          process.exitCode = 1;
          return;
        }

        const deploymentName = (await prompt('  Deployment name: ')).trim();
        if (!deploymentName) {
          console.log('\n  No deployment name provided. Aborting.\n');
          process.exitCode = 1;
          return;
        }

        // API key — use env var if available
        const envKey = process.env.ONTOINDEX_API_KEY || process.env.OPENAI_API_KEY || '';
        let azureKey: string;
        if (envKey) {
          const masked = envKey.slice(0, 6) + '...' + envKey.slice(-4);
          const useEnv = await prompt(`  Use existing env key (${masked})? (Y/n): `);
          if (!useEnv || useEnv.toLowerCase() === 'y' || useEnv.toLowerCase() === 'yes') {
            azureKey = envKey;
          } else {
            azureKey = await prompt('  API key: ', true);
          }
        } else {
          azureKey = await prompt('  API key: ', true);
        }

        if (!azureKey) {
          console.log('\n  No key provided. Aborting.\n');
          process.exitCode = 1;
          return;
        }

        // Always use v1 API format — no need for api-version
        const azureBaseUrl = `${endpoint}/openai/v1`;

        await saveCLIConfig({
          apiKey: azureKey,
          baseUrl: azureBaseUrl,
          model: deploymentName,
          provider: 'azure',
        });
        console.log('  Config saved to ~/.ontoindex/config.json\n');

        llmConfig = {
          ...llmConfig,
          apiKey: azureKey,
          baseUrl: azureBaseUrl,
          model: deploymentName,
          provider: 'azure',
        };
      } else {
        // OpenAI-compatible provider (OpenAI, OpenRouter, Custom)
        if (choice === '2') {
          baseUrl = 'https://openrouter.ai/api/v1';
          defaultModel = 'minimax/minimax-m2.5';
          provider = 'openrouter';
        } else if (choice === '4') {
          baseUrl = await prompt('  Base URL (e.g. http://localhost:11434/v1): ');
          if (!baseUrl) {
            console.log('\n  No URL provided. Aborting.\n');
            process.exitCode = 1;
            return;
          }
          defaultModel = 'gpt-4o-mini';
          provider = 'custom';
        } else {
          baseUrl = 'https://api.openai.com/v1';
          defaultModel = 'gpt-4o-mini';
          provider = 'openai';
        }

        // Model
        const modelInput = await prompt(`  Model (default: ${defaultModel}): `);
        const model = modelInput || defaultModel;

        // API key — pre-fill hint if env var exists
        const envKey = process.env.ONTOINDEX_API_KEY || process.env.OPENAI_API_KEY || '';
        if (envKey) {
          const masked = envKey.slice(0, 6) + '...' + envKey.slice(-4);
          const useEnv = await prompt(`  Use existing env key (${masked})? (Y/n): `);
          if (!useEnv || useEnv.toLowerCase() === 'y' || useEnv.toLowerCase() === 'yes') {
            key = envKey;
          } else {
            key = await prompt('  API key: ', true);
          }
        } else {
          key = await prompt('  API key: ', true);
        }

        if (!key) {
          console.log('\n  No key provided. Aborting.\n');
          process.exitCode = 1;
          return;
        }

        // Save
        await saveCLIConfig({ apiKey: key, baseUrl, model, provider });
        console.log('  Config saved to ~/.ontoindex/config.json\n');

        llmConfig = { ...llmConfig, apiKey: key, baseUrl, model, provider };
      }
    }
  }

  // ── Setup progress bar with elapsed timer ──────────────────────────
  const bar = new cliProgress.SingleBar(
    {
      format: '  {bar} {percentage}% | {phase}',
      barCompleteChar: '\u2588',
      barIncompleteChar: '\u2591',
      hideCursor: true,
      barGlue: '',
      autopadding: true,
      clearOnComplete: false,
      stopOnComplete: false,
    },
    cliProgress.Presets.shades_grey,
  );

  bar.start(100, 0, { phase: 'Initializing...' });

  const t0 = Date.now();
  let lastPhase = '';
  let phaseStart = t0;

  // Tick elapsed time every second while stuck on the same phase
  const elapsedTimer = setInterval(() => {
    if (lastPhase) {
      const elapsed = Math.round((Date.now() - phaseStart) / 1000);
      if (elapsed >= 3) {
        bar.update({ phase: `${lastPhase} (${elapsed}s)` });
      }
    }
  }, 1000);

  // ── Run generator ───────────────────────────────────────────────────
  const wikiOptions: WikiOptions = {
    force: options?.force,
    concurrency: options?.concurrency ? parseInt(options.concurrency, 10) : undefined,
    reviewOnly: options?.review,
    maxTokenBudget: options?.maxTokens ? parseInt(options.maxTokens, 10) : undefined,
  };

  const generator = new WikiGenerator(
    repoPath,
    storagePath,
    lbugPath,
    llmConfig,
    wikiOptions,
    (phase, percent, detail) => {
      const label = detail || phase;
      if (label !== lastPhase) {
        lastPhase = label;
        phaseStart = Date.now();
      }
      bar.update(percent, { phase: label });
    },
  );

  try {
    const result = await generator.run();

    clearInterval(elapsedTimer);
    bar.stop();

    const elapsed = ((Date.now() - t0) / 1000).toFixed(1);

    const wikiDir = path.join(storagePath, 'wiki');
    const viewerPath = path.join(wikiDir, 'index.html');
    const treeFile = path.join(wikiDir, 'module_tree.json');

    // Review mode: show module tree and ask for confirmation
    if (options?.review && result.moduleTree) {
      console.log(`\n  Module structure ready for review (${elapsed}s)\n`);
      console.log('  Modules to generate:\n');

      const printTree = (nodes: typeof result.moduleTree, indent = 0) => {
        for (const node of nodes) {
          const prefix = '  '.repeat(indent + 2);
          const fileCount = node.files?.length || 0;
          const childCount = node.children?.length || 0;
          const suffix =
            fileCount > 0
              ? ` (${fileCount} files)`
              : childCount > 0
                ? ` (${childCount} children)`
                : '';
          console.log(`${prefix}- ${node.name}${suffix}`);
          if (node.children && node.children.length > 0) {
            printTree(node.children, indent + 1);
          }
        }
      };
      printTree(result.moduleTree);

      console.log(`\n  Tree saved to: ${treeFile}`);
      console.log('  You can edit this file to remove/rename modules.\n');

      // Ask for confirmation (auto-continue in non-interactive environments)
      if (!process.stdin.isTTY) {
        console.log('  Non-interactive mode — auto-continuing with generation.\n');
      }
      const answer = process.stdin.isTTY
        ? await prompt('  Continue with generation? (Y/n/edit): ')
        : 'y';
      const choice = answer.trim().toLowerCase();

      if (choice === 'n' || choice === 'no') {
        console.log('\n  Generation cancelled. Run `ontoindex wiki` later to generate.\n');
        return;
      }

      if (choice === 'edit' || choice === 'e') {
        // Open editor for the user
        const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
        console.log(`\n  Opening ${treeFile} in ${editor}...`);
        console.log('  Save and close the editor when done.\n');

        try {
          execFileSync(editor, [treeFile], { stdio: 'inherit' });
        } catch {
          console.log(`  Could not open editor. Please edit manually:\n  ${treeFile}\n`);
          console.log('  Then run `ontoindex wiki` to continue.\n');
          return;
        }
      }

      // Continue with generation using the (possibly edited) tree
      console.log('\n  Continuing with wiki generation...\n');
      bar.start(100, 30, { phase: 'Generating pages...' });

      // Re-run generator without reviewOnly flag
      const continueOptions: WikiOptions = {
        ...wikiOptions,
        reviewOnly: false,
      };

      const continueGenerator = new WikiGenerator(
        repoPath,
        storagePath,
        lbugPath,
        llmConfig,
        continueOptions,
        (phase, percent, detail) => {
          const label = detail || phase;
          if (label !== lastPhase) {
            lastPhase = label;
            phaseStart = Date.now();
          }
          bar.update(percent, { phase: label });
        },
      );

      const continueResult = await continueGenerator.run();

      bar.update(100, { phase: 'Done' });
      bar.stop();

      const totalElapsed = ((Date.now() - t0) / 1000).toFixed(1);
      console.log(`\n  Wiki generated successfully (${totalElapsed}s)\n`);
      console.log(`  Mode: ${continueResult.mode}`);
      console.log(`  Pages: ${continueResult.pagesGenerated}`);
      console.log(`  Output: ${wikiDir}`);
      console.log(`  Viewer: ${viewerPath}`);

      if (continueResult.failedModules && continueResult.failedModules.length > 0) {
        console.log(`\n  Failed modules (${continueResult.failedModules.length}):`);
        for (const mod of continueResult.failedModules) {
          console.log(`    - ${mod}`);
        }
      }

      console.log('');
      await maybePublishGist(viewerPath, options?.gist);
      return;
    }

    bar.update(100, { phase: 'Done' });

    if (result.mode === 'up-to-date' && !options?.force) {
      console.log('\n  Wiki is already up to date.');
      console.log(`  Viewer: ${viewerPath}\n`);
      await maybePublishGist(viewerPath, options?.gist);
      return;
    }

    console.log(`\n  Wiki generated successfully (${elapsed}s)\n`);
    console.log(`  Mode: ${result.mode}`);
    console.log(`  Pages: ${result.pagesGenerated}`);
    console.log(`  Output: ${wikiDir}`);
    console.log(`  Viewer: ${viewerPath}`);

    if (result.failedModules && result.failedModules.length > 0) {
      console.log(`\n  Failed modules (${result.failedModules.length}):`);
      for (const mod of result.failedModules) {
        console.log(`    - ${mod}`);
      }
      console.log('  Re-run to retry failed modules (pages will be regenerated).');
    }

    console.log('');

    await maybePublishGist(viewerPath, options?.gist);
  } catch (err: unknown) {
    clearInterval(elapsedTimer);
    bar.stop();

    const message = errorMessage(err);

    if (message?.includes('No source files')) {
      console.log(`\n  ${message}\n`);
    } else if (message?.includes('content filter')) {
      // Content filter block — actionable message
      console.log(`\n  Content Filter: ${message}\n`);
      console.log(
        '  To resolve: rephrase your prompt or adjust the content filter policy for your deployment.\n',
      );
    } else if (message?.includes('API key') || message?.includes('API error')) {
      console.log(`\n  LLM Error: ${message}\n`);

      // Offer to reconfigure on auth-related failures
      const isAuthError =
        message?.includes('401') ||
        message?.includes('403') ||
        message?.includes('502') ||
        message?.includes('authenticate') ||
        message?.includes('Unauthorized');
      if (isAuthError && process.stdin.isTTY) {
        const answer = await new Promise<string>((resolve) => {
          const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
          rl.question('  Reconfigure LLM settings? (Y/n): ', (ans) => {
            rl.close();
            resolve(ans.trim().toLowerCase());
          });
        });
        if (!answer || answer === 'y' || answer === 'yes') {
          // Clear saved config so next run triggers interactive setup
          await saveCLIConfig({});
          console.log('  Config cleared. Run `ontoindex wiki` again to reconfigure.\n');
        }
      }
    } else {
      console.log(`\n  Error: ${message}\n`);
      if (process.env.ONTOINDEX_VERBOSE) {
        console.error(err);
      }
    }
    process.exitCode = 1;
  }
};

// ─── Gist Publishing ───────────────────────────────────────────────────

function hasGhCLI(): boolean {
  try {
    execFileSync('gh', ['--version'], {
      stdio: 'ignore',
      timeout: 3_000,
      maxBuffer: 1024 * 1024,
    });
    return true;
  } catch {
    return false;
  }
}

function publishGist(htmlPath: string): { url: string; rawUrl: string } | null {
  try {
    const output = execFileSync(
      'gh',
      [
        'gist',
        'create',
        htmlPath,
        '--desc',
        'Repository Wiki — generated by OntoIndex',
        '--public',
      ],
      {
        encoding: 'utf-8',
        stdio: ['pipe', 'pipe', 'pipe'],
        timeout: 15_000,
        maxBuffer: 1024 * 1024,
      },
    ).trim();

    // gh gist create prints the gist URL as the last line
    const lines = output.split('\n');
    const gistUrl = lines.find((l) => l.includes('gist.github.com')) || lines[lines.length - 1];

    if (!gistUrl || !gistUrl.includes('gist.github.com')) return null;

    // Build a raw viewer URL via gist.githack.com
    // gist URL format: https://gist.github.com/{user}/{id}
    const match = gistUrl.match(/gist\.github\.com\/([^/]+)\/([a-f0-9]+)/);
    let rawUrl = gistUrl;
    if (match) {
      rawUrl = `https://gistcdn.githack.com/${match[1]}/${match[2]}/raw/index.html`;
    }

    return { url: gistUrl.trim(), rawUrl };
  } catch {
    return null;
  }
}

async function maybePublishGist(htmlPath: string, gistFlag?: boolean): Promise<void> {
  if (gistFlag === false) return;

  // Check that the HTML file exists
  try {
    const fs = await import('fs/promises');
    await fs.access(htmlPath);
  } catch {
    return;
  }

  if (!hasGhCLI()) {
    if (gistFlag) {
      console.log('  GitHub CLI (gh) is not installed. Cannot publish gist.');
      console.log('  Install it: https://cli.github.com\n');
    }
    return;
  }

  let shouldPublish = !!gistFlag;

  if (!shouldPublish && process.stdin.isTTY) {
    const answer = await new Promise<string>((resolve) => {
      const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
      rl.question('  Publish wiki as a GitHub Gist for easy viewing? (Y/n): ', (ans) => {
        rl.close();
        resolve(ans.trim().toLowerCase());
      });
    });
    shouldPublish = !answer || answer === 'y' || answer === 'yes';
  }

  if (!shouldPublish) return;

  console.log('\n  Publishing to GitHub Gist...');
  const result = publishGist(htmlPath);

  if (result) {
    console.log(`  Gist:   ${result.url}`);
    console.log(`  Viewer: ${result.rawUrl}\n`);
  } else {
    console.log('  Failed to publish gist. Make sure `gh auth login` is configured.\n');
  }
}
