/**
 * Comprehensive Error Handling System
 * Provides better error messages, recovery strategies, and user guidance
 */

export enum ErrorCode {
  // Validation Errors
  INVALID_WORKFLOW_STRUCTURE = 'INVALID_WORKFLOW_STRUCTURE',
  MISSING_REQUIRED_FIELD = 'MISSING_REQUIRED_FIELD',
  INVALID_COMPONENT_TYPE = 'INVALID_COMPONENT_TYPE',
  INVALID_DEPENDENCY_REFERENCE = 'INVALID_DEPENDENCY_REFERENCE',
  CIRCULAR_DEPENDENCY = 'CIRCULAR_DEPENDENCY',

  // File System Errors
  FILE_NOT_FOUND = 'FILE_NOT_FOUND',
  FILE_WRITE_ERROR = 'FILE_WRITE_ERROR',
  PERMISSION_DENIED = 'PERMISSION_DENIED',
  DIRECTORY_NOT_EMPTY = 'DIRECTORY_NOT_EMPTY',

  // Generation Errors
  GENERATOR_FAILED = 'GENERATOR_FAILED',
  TEMPLATE_ERROR = 'TEMPLATE_ERROR',
  PARTIAL_GENERATION = 'PARTIAL_GENERATION',

  // Infrastructure Errors
  SERVICE_CONNECTION_FAILED = 'SERVICE_CONNECTION_FAILED',
  DATABASE_ERROR = 'DATABASE_ERROR',
  NETWORK_ERROR = 'NETWORK_ERROR',

  // Git Errors
  NOT_A_GIT_REPO = 'NOT_A_GIT_REPO',
  UNCOMMITTED_CHANGES = 'UNCOMMITTED_CHANGES',
  GIT_OPERATION_FAILED = 'GIT_OPERATION_FAILED',

  // Version Management Errors
  VERSION_ALREADY_EXISTS = 'VERSION_ALREADY_EXISTS',
  VERSION_NOT_FOUND = 'VERSION_NOT_FOUND',
  INVALID_SEMVER = 'INVALID_SEMVER',
  MANIFEST_MISMATCH = 'MANIFEST_MISMATCH',
}

export enum ErrorSeverity {
  FATAL = 'FATAL',       // Cannot continue, must abort
  ERROR = 'ERROR',       // Can potentially recover
  WARNING = 'WARNING',   // Can continue with caution
  INFO = 'INFO',         // Informational only
}

export interface RecoveryStrategy {
  description: string;
  automated: boolean;
  steps: string[];
  command?: string;
}

export interface BangstackError {
  code: ErrorCode;
  severity: ErrorSeverity;
  message: string;
  details?: string;
  context?: Record<string, any>;
  recoveryStrategies?: RecoveryStrategy[];
  relatedErrors?: BangstackError[];
}

/**
 * Enhanced error class with recovery suggestions
 */
export class EnhancedError extends Error {
  public readonly code: ErrorCode;
  public readonly severity: ErrorSeverity;
  public readonly details?: string;
  public readonly context?: Record<string, any>;
  public readonly recoveryStrategies: RecoveryStrategy[];
  public readonly relatedErrors: BangstackError[];

  constructor(error: BangstackError) {
    super(error.message);
    this.name = 'BangstackError';
    this.code = error.code;
    this.severity = error.severity;
    this.details = error.details;
    this.context = error.context;
    this.recoveryStrategies = error.recoveryStrategies || [];
    this.relatedErrors = error.relatedErrors || [];
  }

  /**
   * Format error for display to user
   */
  format(): string {
    const lines: string[] = [];

    // Header with severity
    const severityIcon = {
      [ErrorSeverity.FATAL]: '❌',
      [ErrorSeverity.ERROR]: '🔴',
      [ErrorSeverity.WARNING]: '⚠️',
      [ErrorSeverity.INFO]: 'ℹ️',
    };

    lines.push(`\n${severityIcon[this.severity]} ${this.severity}: ${this.message}`);

    // Error code
    lines.push(`   Code: ${this.code}`);

    // Details if present
    if (this.details) {
      lines.push(`\n   ${this.details}`);
    }

    // Context information
    if (this.context && Object.keys(this.context).length > 0) {
      lines.push('\n   Context:');
      for (const [key, value] of Object.entries(this.context)) {
        lines.push(`     ${key}: ${JSON.stringify(value)}`);
      }
    }

    // Recovery strategies
    if (this.recoveryStrategies.length > 0) {
      lines.push('\n   💡 How to fix:');
      this.recoveryStrategies.forEach((strategy, index) => {
        lines.push(`\n   ${index + 1}. ${strategy.description}`);
        if (strategy.steps.length > 0) {
          strategy.steps.forEach(step => {
            lines.push(`      • ${step}`);
          });
        }
        if (strategy.command) {
          lines.push(`      $ ${strategy.command}`);
        }
      });
    }

    // Related errors
    if (this.relatedErrors.length > 0) {
      lines.push('\n   Related issues:');
      this.relatedErrors.forEach(err => {
        lines.push(`     • ${err.message}`);
      });
    }

    lines.push(''); // Empty line at end

    return lines.join('\n');
  }

  /**
   * Check if error is recoverable
   */
  isRecoverable(): boolean {
    return this.severity !== ErrorSeverity.FATAL &&
           this.recoveryStrategies.some(s => s.automated);
  }

  /**
   * Get automated recovery strategy if available
   */
  getAutomatedRecovery(): RecoveryStrategy | null {
    return this.recoveryStrategies.find(s => s.automated) || null;
  }
}

/**
 * Error factory for creating specific error types
 */
export class ErrorFactory {
  static invalidWorkflowStructure(missingField: string, location?: string): EnhancedError {
    return new EnhancedError({
      code: ErrorCode.INVALID_WORKFLOW_STRUCTURE,
      severity: ErrorSeverity.FATAL,
      message: `Invalid workflow structure: missing required field "${missingField}"`,
      details: location ? `Error found at: ${location}` : undefined,
      context: { missingField, location },
      recoveryStrategies: [
        {
          description: 'Add the required field to your workflow YAML',
          automated: false,
          steps: [
            'Open your workflow file',
            `Add the "${missingField}" field`,
            'Ensure proper YAML formatting',
            'Run bang verify to check',
          ],
        },
        {
          description: 'Use a workflow template',
          automated: false,
          steps: [
            'Run: bang init --template <type>',
            'Templates include all required fields',
          ],
          command: 'bang init --template api',
        },
      ],
    });
  }

  static invalidDependencyReference(
    component: string,
    dependency: string,
    availableComponents: string[]
  ): EnhancedError {
    return new EnhancedError({
      code: ErrorCode.INVALID_DEPENDENCY_REFERENCE,
      severity: ErrorSeverity.ERROR,
      message: `Component "${component}" depends on "${dependency}" which doesn't exist`,
      context: { component, dependency, availableComponents },
      recoveryStrategies: [
        {
          description: 'Add the missing component',
          automated: false,
          steps: [
            `Define the "${dependency}" component in your workflow`,
            'Available components: ' + availableComponents.join(', '),
          ],
        },
        {
          description: 'Remove the invalid dependency',
          automated: false,
          steps: [
            `Remove "${dependency}" from ${component}.dependsOn`,
            'Update component logic if needed',
          ],
        },
      ],
    });
  }

  static fileNotFound(filePath: string, expectedLocation?: string): EnhancedError {
    return new EnhancedError({
      code: ErrorCode.FILE_NOT_FOUND,
      severity: ErrorSeverity.ERROR,
      message: `File not found: ${filePath}`,
      context: { filePath, expectedLocation },
      recoveryStrategies: [
        {
          description: 'Check the file path',
          automated: false,
          steps: [
            'Verify the file exists',
            expectedLocation ? `Expected location: ${expectedLocation}` : 'Check spelling and path',
            'Use absolute or relative paths correctly',
          ],
        },
        {
          description: 'Create the file',
          automated: false,
          steps: [
            'Create the missing file',
            'Run bang generate to create files automatically',
          ],
          command: expectedLocation ? `touch ${expectedLocation}` : undefined,
        },
      ],
    });
  }

  static partialGeneration(
    succeededComponents: string[],
    failedComponents: string[],
    errors: Array<{ component: string; error: string }>
  ): EnhancedError {
    return new EnhancedError({
      code: ErrorCode.PARTIAL_GENERATION,
      severity: ErrorSeverity.WARNING,
      message: 'Generation partially completed - some components failed',
      details: `✓ Succeeded: ${succeededComponents.join(', ')}\n   ✗ Failed: ${failedComponents.join(', ')}`,
      context: { succeededComponents, failedComponents },
      recoveryStrategies: [
        {
          description: 'Retry failed components',
          automated: true,
          steps: [
            'Fix the errors listed below',
            'Re-run generation for failed components only',
            'Bangstack will skip already generated components',
          ],
          command: 'bang generate <workflow> --retry',
        },
        {
          description: 'Review and fix errors manually',
          automated: false,
          steps: errors.map(e => `${e.component}: ${e.error}`),
        },
      ],
      relatedErrors: errors.map(e => ({
        code: ErrorCode.GENERATOR_FAILED,
        severity: ErrorSeverity.ERROR,
        message: `${e.component}: ${e.error}`,
      })),
    });
  }

  static uncommittedChanges(files: string[]): EnhancedError {
    return new EnhancedError({
      code: ErrorCode.UNCOMMITTED_CHANGES,
      severity: ErrorSeverity.WARNING,
      message: 'Repository has uncommitted changes',
      details: `${files.length} file(s) with uncommitted changes`,
      context: { files },
      recoveryStrategies: [
        {
          description: 'Commit your changes',
          automated: false,
          steps: [
            'Review your changes: git diff',
            'Stage files: git add .',
            'Commit: git commit -m "Your message"',
          ],
          command: 'git add . && git commit -m "chore: commit changes"',
        },
        {
          description: 'Stash your changes',
          automated: false,
          steps: [
            'Temporarily save changes: git stash',
            'Complete the operation',
            'Restore changes: git stash pop',
          ],
          command: 'git stash',
        },
        {
          description: 'Force the operation (not recommended)',
          automated: false,
          steps: [
            'Use --force flag to bypass check',
            'Warning: This may lead to data loss',
          ],
          command: 'bang <command> --force',
        },
      ],
    });
  }

  static versionAlreadyExists(version: string, existingInfo: { commit: string; date: string }): EnhancedError {
    return new EnhancedError({
      code: ErrorCode.VERSION_ALREADY_EXISTS,
      severity: ErrorSeverity.ERROR,
      message: `Version ${version} already exists`,
      details: `Existing version: ${version}\nCommit: ${existingInfo.commit}\nDate: ${existingInfo.date}`,
      context: { version, ...existingInfo },
      recoveryStrategies: [
        {
          description: 'Use a different version number',
          automated: false,
          steps: [
            'Check existing versions: bang version list',
            'Choose a new version number',
            'Follow semantic versioning (MAJOR.MINOR.PATCH)',
          ],
          command: 'bang version list',
        },
        {
          description: 'Delete the existing version (dangerous)',
          automated: false,
          steps: [
            'Only do this if the version was created in error',
            `Delete tag: git tag -d ${version}`,
            'Delete from remote: git push origin :refs/tags/${version}',
          ],
          command: `git tag -d ${version}`,
        },
      ],
    });
  }

  static manifestMismatch(expected: string[], detected: string[]): EnhancedError {
    const missing = expected.filter(s => !detected.includes(s));
    const unexpected = detected.filter(s => !expected.includes(s));

    return new EnhancedError({
      code: ErrorCode.MANIFEST_MISMATCH,
      severity: ErrorSeverity.WARNING,
      message: 'Dependency mismatch between workflow and code',
      details: missing.length > 0
        ? `Missing: ${missing.join(', ')}\nUnexpected: ${unexpected.join(', ')}`
        : `Unexpected: ${unexpected.join(', ')}`,
      context: { expected, detected, missing, unexpected },
      recoveryStrategies: [
        {
          description: 'Update workflow to match code',
          automated: false,
          steps: [
            'Add missing services to expected_dependencies',
            'Or remove unexpected imports from code',
          ],
        },
        {
          description: 'Regenerate code to match workflow',
          automated: true,
          steps: [
            'Run bang generate to update code',
            'This will add/remove service clients',
          ],
          command: 'bang generate <workflow>',
        },
        {
          description: 'Run verification for detailed report',
          automated: false,
          steps: [
            'Run: bang verify <workflow>',
            'Get detailed analysis and recommendations',
          ],
          command: 'bang verify <workflow>',
        },
      ],
    });
  }

  static serviceConnectionFailed(service: string, provider: string, error: string): EnhancedError {
    return new EnhancedError({
      code: ErrorCode.SERVICE_CONNECTION_FAILED,
      severity: ErrorSeverity.ERROR,
      message: `Failed to connect to ${service} (${provider})`,
      details: error,
      context: { service, provider, error },
      recoveryStrategies: [
        {
          description: 'Check service is running',
          automated: false,
          steps: [
            'For local services: docker-compose ps',
            'Ensure service is healthy',
            'Check service logs',
          ],
          command: 'docker-compose ps',
        },
        {
          description: 'Verify connection credentials',
          automated: false,
          steps: [
            'Check environment variables',
            'Verify API keys/connection strings',
            'Test connection manually',
          ],
        },
        {
          description: 'Check network connectivity',
          automated: false,
          steps: [
            'Verify service URL is accessible',
            'Check firewall rules',
            'Ensure no proxy issues',
          ],
        },
      ],
    });
  }
}

/**
 * Error aggregator for collecting multiple errors
 */
export class ErrorAggregator {
  private errors: BangstackError[] = [];
  private warnings: BangstackError[] = [];

  add(error: BangstackError) {
    if (error.severity === ErrorSeverity.WARNING || error.severity === ErrorSeverity.INFO) {
      this.warnings.push(error);
    } else {
      this.errors.push(error);
    }
  }

  hasErrors(): boolean {
    return this.errors.length > 0;
  }

  hasWarnings(): boolean {
    return this.warnings.length > 0;
  }

  getErrors(): BangstackError[] {
    return this.errors;
  }

  getWarnings(): BangstackError[] {
    return this.warnings;
  }

  format(): string {
    const lines: string[] = [];

    if (this.errors.length > 0) {
      lines.push('\n❌ Errors:\n');
      this.errors.forEach((error, index) => {
        const enhanced = new EnhancedError(error);
        lines.push(`${index + 1}. ${enhanced.format()}`);
      });
    }

    if (this.warnings.length > 0) {
      lines.push('\n⚠️  Warnings:\n');
      this.warnings.forEach((warning, index) => {
        const enhanced = new EnhancedError(warning);
        lines.push(`${index + 1}. ${enhanced.format()}`);
      });
    }

    return lines.join('\n');
  }

  clear() {
    this.errors = [];
    this.warnings = [];
  }
}
