/**
 * Error recovery and retry logic for agent execution
 */

export type ErrorType = 'network' | 'ssh_auth' | 'command_timeout' | 'llm_api' | 'validation' | 'unknown'

export interface RetryStrategy {
  maxRetries: number
  backoffMs: number[]
  shouldRetry: (error: Error, attempt: number) => boolean
}

/**
 * Classify error type
 */
export function classifyError(error: Error): ErrorType {
  const message = error.message.toLowerCase()
  
  if (message.includes('network') || message.includes('fetch') || message.includes('econnrefused')) {
    return 'network'
  }
  if (message.includes('authentication') || message.includes('permission denied')) {
    return 'ssh_auth'
  }
  if (message.includes('timeout') || message.includes('timed out')) {
    return 'command_timeout'
  }
  if (message.includes('api') || message.includes('rate limit') || message.includes('quota')) {
    return 'llm_api'
  }
  if (message.includes('validation') || message.includes('invalid')) {
    return 'validation'
  }
  
  return 'unknown'
}

/**
 * Get retry strategy based on error type
 */
export function getRetryStrategy(errorType: ErrorType): RetryStrategy {
  switch (errorType) {
    case 'network':
      return {
        maxRetries: 3,
        backoffMs: [1000, 2000, 4000], // Exponential backoff
        shouldRetry: () => true,
      }
      
    case 'ssh_auth':
      return {
        maxRetries: 1,
        backoffMs: [2000],
        shouldRetry: () => true, // Try once to re-establish connection
      }
      
    case 'command_timeout':
      return {
        maxRetries: 2,
        backoffMs: [3000, 5000],
        shouldRetry: () => true,
      }
      
    case 'llm_api':
      return {
        maxRetries: 3,
        backoffMs: [2000, 5000, 10000],
        shouldRetry: (error, attempt) => {
          // Don't retry if quota exceeded
          if (error.message.includes('quota')) return false
          return attempt < 3
        },
      }
      
    case 'validation':
      return {
        maxRetries: 0,
        backoffMs: [],
        shouldRetry: () => false, // Validation errors shouldn't be retried
      }
      
    default:
      return {
        maxRetries: 1,
        backoffMs: [2000],
        shouldRetry: () => true,
      }
  }
}

/**
 * Execute with retry logic
 */
export async function executeWithRetry<T>(
  fn: () => Promise<T>,
  errorType?: ErrorType
): Promise<T> {
  let lastError: Error | null = null
  let attempt = 0
  
  while (true) {
    try {
      return await fn()
    } catch (error) {
      lastError = error instanceof Error ? error : new Error(String(error))
      const type = errorType || classifyError(lastError)
      const strategy = getRetryStrategy(type)
      
      if (attempt >= strategy.maxRetries || !strategy.shouldRetry(lastError, attempt)) {
        throw lastError
      }
      
      // Wait before retry
      const backoff = strategy.backoffMs[attempt] || strategy.backoffMs[strategy.backoffMs.length - 1]
      await new Promise(resolve => setTimeout(resolve, backoff))
      
      attempt++
    }
  }
}

/**
 * Cost tracking for LLM API calls
 */
export class CostTracker {
  private totalTokens = 0
  private totalCost = 0
  private callCount = 0
  
  // Approximate costs per 1M tokens (as of 2025)
  private readonly costPerMillion: Record<string, { input: number; output: number }> = {
    'openai/gpt-4o': { input: 2.50, output: 10.00 },
    'openai/gpt-4o-mini': { input: 0.15, output: 0.60 },
    'anthropic/claude-3.5-sonnet': { input: 3.00, output: 15.00 },
    'anthropic/claude-3-haiku': { input: 0.25, output: 1.25 },
    'meta-llama/llama-3.1-70b-instruct': { input: 0.35, output: 0.40 },
    'deepseek/deepseek-chat': { input: 0.14, output: 0.28 },
  }
  
  trackCall(modelName: string, inputTokens: number, outputTokens: number) {
    this.callCount++
    this.totalTokens += inputTokens + outputTokens
    
    const costs = this.costPerMillion[modelName] || { input: 1.00, output: 2.00 }
    const cost = (inputTokens * costs.input + outputTokens * costs.output) / 1_000_000
    this.totalCost += cost
  }
  
  getStats() {
    return {
      callCount: this.callCount,
      totalTokens: this.totalTokens,
      totalCost: this.totalCost,
      averageCostPerCall: this.callCount > 0 ? this.totalCost / this.callCount : 0,
    }
  }
  
  exceedsLimit(limitUsd: number): boolean {
    return this.totalCost >= limitUsd
  }
}

