import chalk from 'chalk';
import { MODEL_LIST, OCO_AI_PROVIDER_ENUM } from '../commands/config';

// Provider billing/help URLs for common errors
export const PROVIDER_BILLING_URLS: Record<string, string | null> = {
  [OCO_AI_PROVIDER_ENUM.ANTHROPIC]:
    'https://console.anthropic.com/settings/billing',
  [OCO_AI_PROVIDER_ENUM.OPENAI]:
    'https://platform.openai.com/settings/organization/billing',
  [OCO_AI_PROVIDER_ENUM.GEMINI]: 'https://aistudio.google.com/app/plan',
  [OCO_AI_PROVIDER_ENUM.GROQ]: 'https://console.groq.com/settings/billing',
  [OCO_AI_PROVIDER_ENUM.MISTRAL]: 'https://console.mistral.ai/billing/',
  [OCO_AI_PROVIDER_ENUM.DEEPSEEK]: 'https://platform.deepseek.com/usage',
  [OCO_AI_PROVIDER_ENUM.OPENROUTER]: 'https://openrouter.ai/credits',
  [OCO_AI_PROVIDER_ENUM.AIMLAPI]: 'https://aimlapi.com/app/billing',
  [OCO_AI_PROVIDER_ENUM.AZURE]:
    'https://portal.azure.com/#view/Microsoft_Azure_CostManagement',
  [OCO_AI_PROVIDER_ENUM.OLLAMA]: null,
  [OCO_AI_PROVIDER_ENUM.MLX]: null,
  [OCO_AI_PROVIDER_ENUM.FLOWISE]: null,
  [OCO_AI_PROVIDER_ENUM.TEST]: null
};

// Error type for insufficient credits/quota
export class InsufficientCreditsError extends Error {
  public readonly provider: string;

  constructor(provider: string, message?: string) {
    super(
      message || `Insufficient credits or quota for provider '${provider}'`
    );
    this.name = 'InsufficientCreditsError';
    this.provider = provider;
  }
}

// Error type for rate limiting (429 errors)
export class RateLimitError extends Error {
  public readonly provider: string;
  public readonly retryAfter?: number;

  constructor(provider: string, retryAfter?: number, message?: string) {
    super(message || `Rate limit exceeded for provider '${provider}'`);
    this.name = 'RateLimitError';
    this.provider = provider;
    this.retryAfter = retryAfter;
  }
}

// Error type for service unavailable (5xx errors)
export class ServiceUnavailableError extends Error {
  public readonly provider: string;
  public readonly statusCode: number;

  constructor(provider: string, statusCode: number = 503, message?: string) {
    super(message || `Service unavailable for provider '${provider}'`);
    this.name = 'ServiceUnavailableError';
    this.provider = provider;
    this.statusCode = statusCode;
  }
}

// Error type for authentication failures
export class AuthenticationError extends Error {
  public readonly provider: string;

  constructor(provider: string, message?: string) {
    super(message || `Authentication failed for provider '${provider}'`);
    this.name = 'AuthenticationError';
    this.provider = provider;
  }
}

export class ModelNotFoundError extends Error {
  public readonly modelName: string;
  public readonly provider: string;
  public readonly statusCode: number;

  constructor(modelName: string, provider: string, statusCode: number = 404) {
    super(`Model '${modelName}' not found for provider '${provider}'`);
    this.name = 'ModelNotFoundError';
    this.modelName = modelName;
    this.provider = provider;
    this.statusCode = statusCode;
  }
}

export class ApiKeyMissingError extends Error {
  public readonly provider: string;

  constructor(provider: string) {
    super(`API key is missing for provider '${provider}'`);
    this.name = 'ApiKeyMissingError';
    this.provider = provider;
  }
}

export function isModelNotFoundError(error: unknown): boolean {
  if (error instanceof ModelNotFoundError) {
    return true;
  }

  if (error instanceof Error) {
    const message = error.message.toLowerCase();

    // OpenAI error patterns
    if (
      message.includes('model') &&
      (message.includes('not found') ||
        message.includes('does not exist') ||
        message.includes('invalid model'))
    ) {
      return true;
    }

    // Anthropic error patterns
    if (
      message.includes('model') &&
      (message.includes('not found') || message.includes('invalid'))
    ) {
      return true;
    }

    // Check for 404 status in axios/fetch errors
    if (
      'status' in (error as any) &&
      (error as any).status === 404 &&
      message.includes('model')
    ) {
      return true;
    }

    // Check for response status
    if ('response' in (error as any)) {
      const response = (error as any).response;
      if (response?.status === 404) {
        return true;
      }
    }
  }

  return false;
}

export function isApiKeyError(error: unknown): boolean {
  if (error instanceof ApiKeyMissingError) {
    return true;
  }

  if (error instanceof Error) {
    const message = error.message.toLowerCase();

    // Common API key error patterns
    if (
      message.includes('api key') ||
      message.includes('apikey') ||
      message.includes('authentication') ||
      message.includes('unauthorized') ||
      message.includes('invalid_api_key') ||
      message.includes('incorrect api key')
    ) {
      return true;
    }

    // Check for 401 status
    if ('response' in (error as any)) {
      const response = (error as any).response;
      if (response?.status === 401) {
        return true;
      }
    }
  }

  return false;
}

export function getSuggestedModels(
  provider: string,
  failedModel: string
): string[] {
  const providerKey = provider.toLowerCase() as keyof typeof MODEL_LIST;
  const models = MODEL_LIST[providerKey];

  if (!models || !Array.isArray(models)) {
    return [];
  }

  // Return first 5 models as suggestions, excluding the failed one
  return models.filter((m) => m !== failedModel).slice(0, 5);
}

export function getRecommendedModel(provider: string): string | null {
  switch (provider.toLowerCase()) {
    case OCO_AI_PROVIDER_ENUM.OPENAI:
      return 'gpt-4o-mini';
    case OCO_AI_PROVIDER_ENUM.ANTHROPIC:
      return 'claude-sonnet-4-20250514';
    case OCO_AI_PROVIDER_ENUM.GEMINI:
      return 'gemini-1.5-flash';
    case OCO_AI_PROVIDER_ENUM.GROQ:
      return 'llama3-70b-8192';
    case OCO_AI_PROVIDER_ENUM.MISTRAL:
      return 'mistral-small-latest';
    case OCO_AI_PROVIDER_ENUM.DEEPSEEK:
      return 'deepseek-chat';
    case OCO_AI_PROVIDER_ENUM.OPENROUTER:
      return 'openai/gpt-4o-mini';
    case OCO_AI_PROVIDER_ENUM.AIMLAPI:
      return 'gpt-4o-mini';
    default:
      return null;
  }
}

export function formatErrorWithRecovery(
  error: Error,
  provider: string,
  model: string
): string {
  const suggestions = getSuggestedModels(provider, model);
  const recommended = getRecommendedModel(provider);

  let message = `\n${error.message}\n`;

  if (suggestions.length > 0) {
    message += '\nSuggested alternatives:\n';
    suggestions.forEach((m, i) => {
      const isRecommended = m === recommended;
      message += `  ${i + 1}. ${m}${isRecommended ? ' (Recommended)' : ''}\n`;
    });
  }

  message += '\nTo fix this, run: oco config set OCO_MODEL=<model-name>\n';
  message += 'Or run: oco setup\n';

  return message;
}

// Detect insufficient credits/quota errors from various providers
export function isInsufficientCreditsError(error: unknown): boolean {
  if (error instanceof InsufficientCreditsError) {
    return true;
  }

  if (error instanceof Error) {
    const message = error.message.toLowerCase();

    // Common patterns for insufficient credits/quota
    if (
      message.includes('insufficient') ||
      message.includes('credit') ||
      message.includes('quota') ||
      message.includes('balance') ||
      message.includes('billing') ||
      message.includes('payment') ||
      message.includes('exceeded') ||
      message.includes('limit reached') ||
      message.includes('no remaining')
    ) {
      return true;
    }

    // Check for 402 Payment Required status
    if ('status' in (error as any) && (error as any).status === 402) {
      return true;
    }

    if ('response' in (error as any)) {
      const response = (error as any).response;
      if (response?.status === 402) {
        return true;
      }
    }
  }

  return false;
}

// Detect rate limit errors (429)
export function isRateLimitError(error: unknown): boolean {
  if (error instanceof RateLimitError) {
    return true;
  }

  if (error instanceof Error) {
    const message = error.message.toLowerCase();

    // Common patterns for rate limiting
    if (
      message.includes('rate limit') ||
      message.includes('rate_limit') ||
      message.includes('too many requests') ||
      message.includes('throttle')
    ) {
      return true;
    }

    // Check for 429 status
    if ('status' in (error as any) && (error as any).status === 429) {
      return true;
    }

    if ('response' in (error as any)) {
      const response = (error as any).response;
      if (response?.status === 429) {
        return true;
      }
    }
  }

  return false;
}

// Detect service unavailable errors (5xx)
export function isServiceUnavailableError(error: unknown): boolean {
  if (error instanceof ServiceUnavailableError) {
    return true;
  }

  if (error instanceof Error) {
    const message = error.message.toLowerCase();

    // Common patterns for service unavailable
    if (
      message.includes('service unavailable') ||
      message.includes('server error') ||
      message.includes('internal error') ||
      message.includes('temporarily unavailable') ||
      message.includes('overloaded')
    ) {
      return true;
    }

    // Check for 5xx status
    const status = (error as any).status || (error as any).response?.status;
    if (status && status >= 500 && status < 600) {
      return true;
    }
  }

  return false;
}

// User-friendly formatted error structure
export interface FormattedError {
  title: string;
  message: string;
  helpUrl: string | null;
  suggestion: string | null;
}

export interface ErrorFormattingContext {
  baseURL?: string;
}

function getCustomEndpointLabel(baseURL?: string): string | null {
  if (!baseURL) {
    return null;
  }

  try {
    return new URL(baseURL).host;
  } catch {
    return null;
  }
}

function getServiceUnavailableMessage(
  provider: string,
  context?: ErrorFormattingContext
): string {
  const endpointLabel = getCustomEndpointLabel(context?.baseURL);

  if (endpointLabel) {
    return `The configured API endpoint (${endpointLabel}) is temporarily unavailable.`;
  }

  if (context?.baseURL) {
    return 'The configured API endpoint is temporarily unavailable.';
  }

  return `The ${provider} service is temporarily unavailable.`;
}

// Format an error into a user-friendly structure
export function formatUserFriendlyError(
  error: unknown,
  provider: string,
  context?: ErrorFormattingContext
): FormattedError {
  const billingUrl = PROVIDER_BILLING_URLS[provider] || null;

  // Handle our custom error types first
  if (error instanceof InsufficientCreditsError) {
    return {
      title: 'Insufficient Credits',
      message: `Your ${provider} account has insufficient credits or quota.`,
      helpUrl: billingUrl,
      suggestion: 'Add credits to your account to continue using the service.'
    };
  }

  if (error instanceof RateLimitError) {
    const retryMsg = error.retryAfter
      ? `Please wait ${error.retryAfter} seconds before retrying.`
      : 'Please wait a moment before retrying.';
    return {
      title: 'Rate Limit Exceeded',
      message: `You've made too many requests to ${provider}.`,
      helpUrl: billingUrl,
      suggestion: retryMsg
    };
  }

  if (error instanceof ServiceUnavailableError) {
    return {
      title: 'Service Unavailable',
      message: getServiceUnavailableMessage(provider, context),
      helpUrl: null,
      suggestion: 'Please try again in a few moments.'
    };
  }

  if (error instanceof AuthenticationError) {
    return {
      title: 'Authentication Failed',
      message: `Your ${provider} API key is invalid or expired.`,
      helpUrl: billingUrl,
      suggestion: 'Run `oco setup` to configure a valid API key.'
    };
  }

  if (error instanceof ModelNotFoundError) {
    return {
      title: 'Model Not Found',
      message: `The model '${error.modelName}' is not available for ${provider}.`,
      helpUrl: null,
      suggestion: 'Run `oco setup` to select a valid model.'
    };
  }

  // Detect error type from raw errors
  if (isInsufficientCreditsError(error)) {
    return {
      title: 'Insufficient Credits',
      message: `Your ${provider} account has insufficient credits or quota.`,
      helpUrl: billingUrl,
      suggestion: 'Add credits to your account to continue using the service.'
    };
  }

  if (isRateLimitError(error)) {
    return {
      title: 'Rate Limit Exceeded',
      message: `You've made too many requests to ${provider}.`,
      helpUrl: billingUrl,
      suggestion: 'Please wait a moment before retrying.'
    };
  }

  if (isServiceUnavailableError(error)) {
    return {
      title: 'Service Unavailable',
      message: getServiceUnavailableMessage(provider, context),
      helpUrl: null,
      suggestion: 'Please try again in a few moments.'
    };
  }

  if (isApiKeyError(error)) {
    return {
      title: 'Authentication Failed',
      message: `Your ${provider} API key is invalid or expired.`,
      helpUrl: billingUrl,
      suggestion: 'Run `oco setup` to configure a valid API key.'
    };
  }

  if (isModelNotFoundError(error)) {
    const model = (error as any).modelName || (error as any).model || 'unknown';
    return {
      title: 'Model Not Found',
      message: `The model '${model}' is not available for ${provider}.`,
      helpUrl: null,
      suggestion: 'Run `oco setup` to select a valid model.'
    };
  }

  // Default: generic error
  const errorMessage = error instanceof Error ? error.message : String(error);
  return {
    title: 'Error',
    message: errorMessage,
    helpUrl: null,
    suggestion: 'Run `oco setup` to reconfigure or check your settings.'
  };
}

// Print a formatted error as a chalk-styled string
export function printFormattedError(formatted: FormattedError): string {
  let output = `\n${chalk.red('✖')} ${chalk.bold.red(formatted.title)}\n`;
  output += `  ${formatted.message}\n`;

  if (formatted.helpUrl) {
    output += `\n  ${chalk.cyan('Help:')} ${chalk.underline(
      formatted.helpUrl
    )}\n`;
  }

  if (formatted.suggestion) {
    output += `\n  ${chalk.yellow('Suggestion:')} ${formatted.suggestion}\n`;
  }

  return output;
}
