/**
 * ErrorAnalyzer Tests — Verify error classification patterns.
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { AgentErrorAnalyzer } from './error-analyzer.js';
import type { SignalManager } from './signal-manager.js';

describe('AgentErrorAnalyzer', () => {
  let errorAnalyzer: AgentErrorAnalyzer;
  let mockSignalManager: SignalManager;

  beforeEach(() => {
    mockSignalManager = {
      clearSignal: vi.fn(),
      checkSignalExists: vi.fn(),
      readSignal: vi.fn(),
      waitForSignal: vi.fn(),
      validateSignalFile: vi.fn(),
    };
    errorAnalyzer = new AgentErrorAnalyzer(mockSignalManager);
  });

  describe('analyzeError', () => {
    describe('auth failure detection', () => {
      it('should detect unauthorized errors', async () => {
        const error = new Error('Unauthorized access');
        const result = await errorAnalyzer.analyzeError(error);

        expect(result.type).toBe('auth_failure');
        expect(result.isTransient).toBe(true);
        expect(result.requiresAccountSwitch).toBe(false);
        expect(result.shouldPersistToDB).toBe(true);
      });

      it('should detect invalid token errors', async () => {
        const error = new Error('Invalid token provided');
        const result = await errorAnalyzer.analyzeError(error);

        expect(result.type).toBe('auth_failure');
        expect(result.isTransient).toBe(true);
      });

      it('should detect 401 errors', async () => {
        const error = new Error('HTTP 401 - Authentication failed');
        const result = await errorAnalyzer.analyzeError(error);

        expect(result.type).toBe('auth_failure');
      });

      it('should detect auth failures in stderr', async () => {
        const error = new Error('Process failed');
        const stderr = 'Error: Authentication failed - expired token';
        const result = await errorAnalyzer.analyzeError(error, null, stderr);

        expect(result.type).toBe('auth_failure');
      });
    });

    describe('usage limit detection', () => {
      it('should detect rate limit errors', async () => {
        const error = new Error('Rate limit exceeded');
        const result = await errorAnalyzer.analyzeError(error);

        expect(result.type).toBe('usage_limit');
        expect(result.isTransient).toBe(false);
        expect(result.requiresAccountSwitch).toBe(true);
        expect(result.shouldPersistToDB).toBe(true);
      });

      it('should detect quota exceeded errors', async () => {
        const error = new Error('Quota exceeded for this month');
        const result = await errorAnalyzer.analyzeError(error);

        expect(result.type).toBe('usage_limit');
      });

      it('should detect 429 errors', async () => {
        const error = new Error('HTTP 429 - Too many requests');
        const result = await errorAnalyzer.analyzeError(error);

        expect(result.type).toBe('usage_limit');
      });

      it('should detect usage limits in stderr', async () => {
        const error = new Error('Request failed');
        const stderr = 'API usage limit reached. Try again later.';
        const result = await errorAnalyzer.analyzeError(error, null, stderr);

        expect(result.type).toBe('usage_limit');
      });
    });

    describe('timeout detection', () => {
      it('should detect timeout errors', async () => {
        const error = new Error('Request timeout');
        const result = await errorAnalyzer.analyzeError(error);

        expect(result.type).toBe('timeout');
        expect(result.isTransient).toBe(true);
        expect(result.requiresAccountSwitch).toBe(false);
      });

      it('should detect timed out errors', async () => {
        const error = new Error('Connection timed out');
        const result = await errorAnalyzer.analyzeError(error);

        expect(result.type).toBe('timeout');
      });
    });

    describe('missing signal detection', () => {
      it('should detect missing signal when process exits successfully', async () => {
        vi.mocked(mockSignalManager.checkSignalExists).mockResolvedValue(false);

        const error = new Error('No output');
        const result = await errorAnalyzer.analyzeError(error, 0, undefined, '/agent/workdir');

        expect(result.type).toBe('missing_signal');
        expect(result.isTransient).toBe(true);
        expect(result.requiresAccountSwitch).toBe(false);
        expect(result.shouldPersistToDB).toBe(false);
        expect(mockSignalManager.checkSignalExists).toHaveBeenCalledWith('/agent/workdir');
      });

      it('should not detect missing signal when signal exists', async () => {
        vi.mocked(mockSignalManager.checkSignalExists).mockResolvedValue(true);

        const error = new Error('No output');
        const result = await errorAnalyzer.analyzeError(error, 0, undefined, '/agent/workdir');

        expect(result.type).toBe('unknown');
      });

      it('should not detect missing signal for non-zero exit codes', async () => {
        const error = new Error('Process failed');
        const result = await errorAnalyzer.analyzeError(error, 1, undefined, '/agent/workdir');

        expect(result.type).toBe('process_crash');
      });
    });

    describe('process crash detection', () => {
      it('should detect crashes with non-zero exit code', async () => {
        const error = new Error('Process exited with code 1');
        const result = await errorAnalyzer.analyzeError(error, 1);

        expect(result.type).toBe('process_crash');
        expect(result.exitCode).toBe(1);
        expect(result.shouldPersistToDB).toBe(true);
      });

      it('should detect transient crashes based on exit code', async () => {
        const error = new Error('Process interrupted');
        const result = await errorAnalyzer.analyzeError(error, 130); // SIGINT

        expect(result.type).toBe('process_crash');
        expect(result.isTransient).toBe(true);
      });

      it('should detect signal-based crashes as transient', async () => {
        const error = new Error('Segmentation fault');
        const result = await errorAnalyzer.analyzeError(error, 139); // SIGSEGV (128+11, signal-based)

        expect(result.type).toBe('process_crash');
        expect(result.isTransient).toBe(true); // signal-based exit codes (128-255) are transient
      });

      it('should detect transient patterns in stderr', async () => {
        const error = new Error('Process failed');
        const stderr = 'Network error: connection refused';
        const result = await errorAnalyzer.analyzeError(error, 1, stderr);

        expect(result.type).toBe('process_crash');
        expect(result.isTransient).toBe(true);
      });
    });

    describe('unknown error handling', () => {
      it('should classify unrecognized errors as unknown', async () => {
        const error = new Error('Something very weird happened');
        const result = await errorAnalyzer.analyzeError(error);

        expect(result.type).toBe('unknown');
        expect(result.isTransient).toBe(false);
        expect(result.requiresAccountSwitch).toBe(false);
        expect(result.shouldPersistToDB).toBe(true);
      });

      it('should handle string errors', async () => {
        const result = await errorAnalyzer.analyzeError('String error message');

        expect(result.type).toBe('unknown');
        expect(result.message).toBe('String error message');
      });
    });

    describe('error context preservation', () => {
      it('should preserve original error object', async () => {
        const originalError = new Error('Test error');
        const result = await errorAnalyzer.analyzeError(originalError);

        expect(result.originalError).toBe(originalError);
      });

      it('should preserve exit code and signal', async () => {
        const error = new Error('Process failed');
        const result = await errorAnalyzer.analyzeError(error, 42, 'stderr output');

        expect(result.exitCode).toBe(42);
      });
    });
  });
});