import {
  isGitRepo,
  getStagedDiff,
  hasStagedChanges,
  createCommit,
} from "@/utils/git";
import {
  error,
  success,
  warning,
  info,
  formatCommitMessage,
  Spinner,
  bold,
  dim,
  formatDiff,
} from "@/utils/formatter";
import { confirm } from "@/utils/prompt";
import { ProviderRegistry } from "@/utils/provider-registry";
import { ResponseParser } from "@/utils/response-parser";
import { formatPrompt } from "@/prompts";
import { ConfigManager } from "@/config/manager";
import { commitMessageSchema } from "@/utils/schemas";
import { OutputFormatter } from "@/utils/output-formatter";
import { mergeWithEnvironment } from "@/utils/cli-options";
import { AI_DEFAULTS } from "@/config/defaults";
import type { CommitMessageResult, CommitOptions } from "./types";

async function generateCommitMessage(
  diff: string,
  options: CommitOptions,
): Promise<CommitMessageResult> {
  // Get the default provider from config if not specified
  const defaultProvider = options.provider || (await ConfigManager.get("defaultProvider"));
  const provider = ProviderRegistry.getProvider(defaultProvider);

  // Get commit configuration
  const config = await ConfigManager.get("commit");

  // Format prompt with variables
  const prompt = formatPrompt("commit", {
    diff,
    conventionalCommits: config.conventionalCommits,
    maxLength: config.maxLength,
    includeEmoji: config.includeEmoji,
    excludePatterns: config.excludePatterns.join(", "),
  });

  let response: string;
  
  // Use custom temperature and maxTokens if provided, with centralized defaults
  const generateOptions = {
    temperature: options.temperature ?? AI_DEFAULTS.temperature.commit,
    maxTokens: options.maxTokens ?? AI_DEFAULTS.maxTokens.commit,
    model: options.model,
  };

  // Use structured output if provider supports it
  if (provider.supportsStructuredOutput?.()) {
    response = await provider.generateStructuredContent?.(
      prompt,
      commitMessageSchema,
      generateOptions,
    ) ?? await provider.generateContent(prompt, generateOptions);
  } else {
    // Fallback to regular generation for providers without structured output
    response = await provider.generateContent(prompt, generateOptions);
  }

  // Parse response into CommitMessageResult
  return ResponseParser.parseCommitMessage(response);
}

export async function commitCommand(options: CommitOptions): Promise<void> {
  // Merge with environment variables
  const mergedOptions = mergeWithEnvironment(options);
  
  // Disable interactive mode if --no-interactive flag is set
  const isInteractive = mergedOptions.interactive !== false && !process.env.CI;
  const outputFormat = mergedOptions.output || 'text';
  
  if (!(await isGitRepo())) {
    if (outputFormat === 'json') {
      console.log(OutputFormatter.formatError("Not a git repository", outputFormat));
    } else {
      error("Not a git repository");
    }
    process.exit(1);
  }

  if (!(await hasStagedChanges())) {
    if (outputFormat === 'json') {
      console.log(OutputFormatter.formatError("No staged changes found", outputFormat));
    } else {
      warning("No staged changes found");
      info("Stage your changes with: git add <files>");
    }
    process.exit(0);
  }

  // Show which provider will be used
  const providerName = mergedOptions.provider || (await ConfigManager.get("defaultProvider"));
  const config = await ConfigManager.load();
  const providerConfig = config.providers[providerName as keyof typeof config.providers];
  
  if (providerConfig) {
    const providerInfo = ProviderRegistry.getProviderInfo(providerName);
    const models = ProviderRegistry.getModelsForProvider(providerName);
    const model = (providerConfig as any).model;
    const modelInfo = Object.entries(models).find(([key, _]) => key === model);
    const modelName = modelInfo ? (modelInfo[1] as any).name || model : model;
    
    const displayName = providerInfo?.name || providerName;
    console.log(dim(`Using ${displayName}/${modelName}`));
  }

  const diff = await getStagedDiff();

  if (mergedOptions.showDiff) {
    console.log("\n" + bold("Staged changes:"));
    console.log(formatDiff(diff));
    console.log();
  }

  const spinner = new Spinner(
    "Analyzing changes and generating commit message...",
  );
  spinner.start();

  try {
    const result = await generateCommitMessage(diff, mergedOptions);
    spinner.stop();

    const message = ResponseParser.formatCommitMessage(result);
    
    // Output based on format
    if (outputFormat === 'json') {
      console.log(OutputFormatter.formatCommitResult(result, 'json'));
      if (!mergedOptions.dryRun && (mergedOptions.yes || !isInteractive)) {
        await createCommit(message);
      }
      return;
    } else if (outputFormat === 'github') {
      console.log(OutputFormatter.formatCommitResult(result, 'github'));
      if (!mergedOptions.dryRun && (mergedOptions.yes || !isInteractive)) {
        await createCommit(message);
      }
      return;
    }
    
    // Default text output
    console.log("\n" + bold("Generated commit message:"));
    console.log(
      "  " + formatCommitMessage(result.type, result.scope, result.subject),
    );

    if (result.body) {
      console.log("\n" + dim("Body:"));
      console.log("  " + dim(result.body));
    }

    console.log();

    if (mergedOptions.dryRun) {
      info("Dry run mode - commit not created");
      return;
    }

    const shouldCommit = mergedOptions.yes || !isInteractive || (await confirm("Create commit?", true));

    if (shouldCommit) {
      await createCommit(message);
      success("Commit created successfully!");
    } else {
      info("Commit cancelled");
    }
  } catch (err) {
    spinner.stop();
    error(`Failed to generate commit message: ${err}`);
    process.exit(1);
  }
}
