/**
 * Retry Handler - Error recovery and retry logic
 *
 * Handles:
 * - Network error detection
 * - Token expiration handling
 * - Quota management
 * - Account switching
 */

import { fail, warn, info } from '../../utils/ui';
import { CLIProxyProvider } from '../types';
import { handleBanDetection, warnPossible403Ban } from '../account-safety';
import { CompositeTierConfig } from '../../config/unified-config-types';

/**
 * Check if error is network-related
 */
export function isNetworkError(error: Error): boolean {
  const networkErrors = [
    'getaddrinfo',
    'ENOTFOUND',
    'ETIMEDOUT',
    'ECONNREFUSED',
    'ENETUNREACH',
    'EAI_AGAIN',
  ];
  return networkErrors.some((errCode) => error.message.includes(errCode));
}

/**
 * Handle network error with user-friendly message
 */
export function handleNetworkError(_error: Error): never {
  console.error('');
  console.error(fail('No network connection detected'));
  console.error('');
  console.error('CLIProxy binary download requires internet access.');
  console.error('Please check your network connection and try again.');
  console.error('');
  process.exit(1);
}

/**
 * Handle token expiration
 */
export async function handleTokenExpiration(
  provider: CLIProxyProvider,
  verbose: boolean
): Promise<void> {
  const { ensureTokenValid } = await import('../auth/token-manager');
  const tokenResult = await ensureTokenValid(provider, verbose);

  if (!tokenResult.valid) {
    // Check if this is an account ban/disable before generic error
    if (tokenResult.error) {
      const { getDefaultAccount } = await import('../account-manager');
      const account = getDefaultAccount(provider);
      if (account) {
        handleBanDetection(provider, account.id, tokenResult.error);
      }
      warnPossible403Ban(provider, tokenResult.error);
    }

    // Token expired and refresh failed - trigger re-auth
    console.error(warn('OAuth token expired and refresh failed'));
    if (tokenResult.error) {
      console.error(`    ${tokenResult.error}`);
    }
    console.error(`    Run "ccs ${provider} --auth" to re-authenticate`);
    process.exit(1);
  }

  if (tokenResult.refreshed && verbose) {
    console.error('[cliproxy] Token was refreshed proactively');
  }
}

/**
 * Handle quota check and auto-switching for providers with quota-based rotation.
 */
export async function handleQuotaCheck(provider: CLIProxyProvider): Promise<void> {
  if (provider !== 'agy' && provider !== 'claude') return;

  const { preflightCheck } = await import('../quota-manager');
  const preflight = await preflightCheck(provider);

  if (!preflight.proceed) {
    console.error(fail(`Cannot start session: ${preflight.reason}`));
    process.exit(1);
  }

  if (preflight.switchedFrom) {
    console.log(info(`Auto-switched to ${preflight.accountId}`));
    console.log(`    Reason: ${preflight.reason}`);
    if (preflight.quotaPercent !== undefined && preflight.quotaPercent !== null) {
      console.log(`    New account quota: ${preflight.quotaPercent.toFixed(1)}%`);
    } else {
      console.log(`    New account quota: N/A (fetch unavailable)`);
    }
  }
}

/** Error patterns indicating provider failure */
export const PROVIDER_ERROR_PATTERNS = [
  /Error:\s*4[0-9]{2}/i,
  /Error:\s*5[0-9]{2}/i,
  /overloaded/i,
  /quota.*exceeded/i,
  /ECONNREFUSED/i,
  /rate.?limit/i,
];

/** Detect which composite tier failed from stderr output */
export function detectFailedTier(
  stderr: string,
  tiers: { opus: CompositeTierConfig; sonnet: CompositeTierConfig; haiku: CompositeTierConfig }
): 'opus' | 'sonnet' | 'haiku' | null {
  for (const tier of ['opus', 'sonnet', 'haiku'] as const) {
    // Strip thinking suffix (e.g., "model(high)" → "model") for matching
    const model = tiers[tier].model.replace(/\([^)]+\)$/, '');
    if (stderr.includes(model)) return tier;
  }
  return null;
}

/** Check if Claude exit indicates provider error (vs normal user exit) */
export function isProviderError(exitCode: number, stderr: string): boolean {
  // Exit code 0 means success, even if stderr has error-like output
  // (could be warnings, debug info, etc.)
  if (exitCode === 0) return false;
  return PROVIDER_ERROR_PATTERNS.some((p) => p.test(stderr));
}
