/**
 * Tests for Error Handler
 */

import {
  ErrorCode,
  ErrorSeverity,
  ErrorFactory,
  EnhancedError,
  ErrorAggregator,
} from './error-handler';

describe('ErrorHandler', () => {
  describe('EnhancedError', () => {
    it('should create an error with basic properties', () => {
      const error = new EnhancedError({
        code: ErrorCode.FILE_NOT_FOUND,
        severity: ErrorSeverity.ERROR,
        message: 'File not found: test.yaml',
      });

      expect(error.code).toBe(ErrorCode.FILE_NOT_FOUND);
      expect(error.severity).toBe(ErrorSeverity.ERROR);
      expect(error.message).toBe('File not found: test.yaml');
    });

    it('should format error with all components', () => {
      const error = new EnhancedError({
        code: ErrorCode.INVALID_WORKFLOW_STRUCTURE,
        severity: ErrorSeverity.FATAL,
        message: 'Invalid workflow structure',
        details: 'Missing required field: stack',
        context: { field: 'stack', location: 'line 10' },
        recoveryStrategies: [
          {
            description: 'Add the required field',
            automated: false,
            steps: ['Open workflow file', 'Add the field'],
          },
        ],
      });

      const formatted = error.format();
      expect(formatted).toContain('❌');
      expect(formatted).toContain('FATAL');
      expect(formatted).toContain('Invalid workflow structure');
      expect(formatted).toContain('Missing required field: stack');
      expect(formatted).toContain('field');
      expect(formatted).toContain('💡 How to fix:');
      expect(formatted).toContain('Add the required field');
    });

    it('should identify recoverable errors', () => {
      const recoverable = new EnhancedError({
        code: ErrorCode.FILE_NOT_FOUND,
        severity: ErrorSeverity.ERROR,
        message: 'File not found',
        recoveryStrategies: [
          {
            description: 'Create the file',
            automated: true,
            steps: ['Create file'],
          },
        ],
      });

      expect(recoverable.isRecoverable()).toBe(true);

      const notRecoverable = new EnhancedError({
        code: ErrorCode.INVALID_WORKFLOW_STRUCTURE,
        severity: ErrorSeverity.FATAL,
        message: 'Fatal error',
      });

      expect(notRecoverable.isRecoverable()).toBe(false);
    });

    it('should get automated recovery strategy', () => {
      const error = new EnhancedError({
        code: ErrorCode.PARTIAL_GENERATION,
        severity: ErrorSeverity.WARNING,
        message: 'Partial generation',
        recoveryStrategies: [
          {
            description: 'Manual fix',
            automated: false,
            steps: [],
          },
          {
            description: 'Retry failed components',
            automated: true,
            steps: ['Run with --retry'],
            command: 'bang generate --retry',
          },
        ],
      });

      const recovery = error.getAutomatedRecovery();
      expect(recovery).toBeDefined();
      expect(recovery?.automated).toBe(true);
      expect(recovery?.command).toBe('bang generate --retry');
    });
  });

  describe('ErrorFactory', () => {
    it('should create invalidWorkflowStructure error', () => {
      const error = ErrorFactory.invalidWorkflowStructure('stack', 'line 10');

      expect(error.code).toBe(ErrorCode.INVALID_WORKFLOW_STRUCTURE);
      expect(error.severity).toBe(ErrorSeverity.FATAL);
      expect(error.message).toContain('stack');
      expect(error.recoveryStrategies.length).toBeGreaterThan(0);
    });

    it('should create invalidDependencyReference error', () => {
      const error = ErrorFactory.invalidDependencyReference(
        'api',
        'redis',
        ['postgres', 'queue']
      );

      expect(error.code).toBe(ErrorCode.INVALID_DEPENDENCY_REFERENCE);
      expect(error.message).toContain('api');
      expect(error.message).toContain('redis');
      expect(error.context?.availableComponents).toEqual(['postgres', 'queue']);
    });

    it('should create fileNotFound error', () => {
      const error = ErrorFactory.fileNotFound('workflow.yaml', '/project');

      expect(error.code).toBe(ErrorCode.FILE_NOT_FOUND);
      expect(error.message).toContain('workflow.yaml');
      expect(error.context?.expectedLocation).toBe('/project');
    });

    it('should create partialGeneration error', () => {
      const error = ErrorFactory.partialGeneration(
        ['api', 'worker'],
        ['frontend'],
        [{ component: 'frontend', error: 'Template error' }]
      );

      expect(error.code).toBe(ErrorCode.PARTIAL_GENERATION);
      expect(error.severity).toBe(ErrorSeverity.WARNING);
      expect(error.message).toContain('partially completed');
      expect(error.relatedErrors.length).toBe(1);
    });

    it('should create uncommittedChanges error', () => {
      const error = ErrorFactory.uncommittedChanges([
        'file1.ts',
        'file2.ts',
        'file3.ts',
      ]);

      expect(error.code).toBe(ErrorCode.UNCOMMITTED_CHANGES);
      expect(error.severity).toBe(ErrorSeverity.WARNING);
      expect(error.context?.files.length).toBe(3);
      expect(error.recoveryStrategies.length).toBeGreaterThan(0);
    });

    it('should create versionAlreadyExists error', () => {
      const error = ErrorFactory.versionAlreadyExists('1.0.0', {
        commit: 'abc123',
        date: '2024-01-15',
      });

      expect(error.code).toBe(ErrorCode.VERSION_ALREADY_EXISTS);
      expect(error.message).toContain('1.0.0');
      expect(error.context?.commit).toBe('abc123');
    });

    it('should create manifestMismatch error', () => {
      const error = ErrorFactory.manifestMismatch(
        ['redis', 'postgres'],
        ['postgres', 'mongodb']
      );

      expect(error.code).toBe(ErrorCode.MANIFEST_MISMATCH);
      expect(error.severity).toBe(ErrorSeverity.WARNING);
      expect(error.context?.missing).toEqual(['redis']);
      expect(error.context?.unexpected).toEqual(['mongodb']);
    });

    it('should create serviceConnectionFailed error', () => {
      const error = ErrorFactory.serviceConnectionFailed(
        'redis',
        'local',
        'ECONNREFUSED'
      );

      expect(error.code).toBe(ErrorCode.SERVICE_CONNECTION_FAILED);
      expect(error.message).toContain('redis');
      expect(error.details).toBe('ECONNREFUSED');
      expect(error.recoveryStrategies.length).toBeGreaterThan(0);
    });
  });

  describe('ErrorAggregator', () => {
    it('should aggregate errors and warnings separately', () => {
      const aggregator = new ErrorAggregator();

      aggregator.add({
        code: ErrorCode.FILE_NOT_FOUND,
        severity: ErrorSeverity.ERROR,
        message: 'Error 1',
      });

      aggregator.add({
        code: ErrorCode.UNCOMMITTED_CHANGES,
        severity: ErrorSeverity.WARNING,
        message: 'Warning 1',
      });

      aggregator.add({
        code: ErrorCode.GENERATOR_FAILED,
        severity: ErrorSeverity.ERROR,
        message: 'Error 2',
      });

      expect(aggregator.hasErrors()).toBe(true);
      expect(aggregator.hasWarnings()).toBe(true);
      expect(aggregator.getErrors().length).toBe(2);
      expect(aggregator.getWarnings().length).toBe(1);
    });

    it('should format all errors and warnings', () => {
      const aggregator = new ErrorAggregator();

      aggregator.add({
        code: ErrorCode.FILE_NOT_FOUND,
        severity: ErrorSeverity.ERROR,
        message: 'Error 1',
      });

      aggregator.add({
        code: ErrorCode.UNCOMMITTED_CHANGES,
        severity: ErrorSeverity.WARNING,
        message: 'Warning 1',
      });

      const formatted = aggregator.format();
      expect(formatted).toContain('❌ Errors:');
      expect(formatted).toContain('⚠️  Warnings:');
      expect(formatted).toContain('Error 1');
      expect(formatted).toContain('Warning 1');
    });

    it('should clear errors and warnings', () => {
      const aggregator = new ErrorAggregator();

      aggregator.add({
        code: ErrorCode.FILE_NOT_FOUND,
        severity: ErrorSeverity.ERROR,
        message: 'Error 1',
      });

      expect(aggregator.hasErrors()).toBe(true);

      aggregator.clear();

      expect(aggregator.hasErrors()).toBe(false);
      expect(aggregator.hasWarnings()).toBe(false);
    });
  });

  describe('Error Severity', () => {
    it('should have correct severity levels', () => {
      expect(ErrorSeverity.FATAL).toBe('FATAL');
      expect(ErrorSeverity.ERROR).toBe('ERROR');
      expect(ErrorSeverity.WARNING).toBe('WARNING');
      expect(ErrorSeverity.INFO).toBe('INFO');
    });
  });

  describe('Recovery Strategies', () => {
    it('should include commands in recovery strategies', () => {
      const error = ErrorFactory.uncommittedChanges(['file1.ts']);
      const strategy = error.recoveryStrategies.find(s => s.command);

      expect(strategy).toBeDefined();
      expect(strategy?.command).toContain('git');
    });

    it('should provide step-by-step instructions', () => {
      const error = ErrorFactory.invalidWorkflowStructure('stack');
      const strategy = error.recoveryStrategies[0];

      expect(strategy.steps.length).toBeGreaterThan(0);
      expect(strategy.steps[0]).toContain('Open');
    });

    it('should distinguish automated vs manual strategies', () => {
      const error = ErrorFactory.partialGeneration(
        ['api'],
        ['frontend'],
        [{ component: 'frontend', error: 'error' }]
      );

      const automated = error.recoveryStrategies.filter(s => s.automated);
      const manual = error.recoveryStrategies.filter(s => !s.automated);

      expect(automated.length).toBeGreaterThan(0);
      expect(manual.length).toBeGreaterThan(0);
    });
  });
});
