/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect } from 'vitest';
import {
  A2AAgentError,
  AgentCardNotFoundError,
  AgentCardAuthError,
  AgentAuthConfigMissingError,
  AgentConnectionError,
  classifyAgentError,
} from './a2a-errors.js';

describe('A2A Error Types', () => {
  describe('A2AAgentError', () => {
    it('should set name, agentName, and userMessage', () => {
      const error = new A2AAgentError('my-agent', 'internal msg', 'user msg');
      expect(error.name).toBe('A2AAgentError');
      expect(error.agentName).toBe('my-agent');
      expect(error.message).toBe('internal msg');
      expect(error.userMessage).toBe('user msg');
    });
  });

  describe('AgentCardNotFoundError', () => {
    it('should produce a user-friendly 404 message', () => {
      const error = new AgentCardNotFoundError(
        'my-agent',
        'https://example.com/card',
      );
      expect(error.name).toBe('AgentCardNotFoundError');
      expect(error.agentName).toBe('my-agent');
      expect(error.userMessage).toContain('404');
      expect(error.userMessage).toContain('https://example.com/card');
      expect(error.userMessage).toContain('agent_card_url');
    });
  });

  describe('AgentCardAuthError', () => {
    it('should produce a user-friendly 401 message', () => {
      const error = new AgentCardAuthError(
        'secure-agent',
        'https://example.com/card',
        401,
      );
      expect(error.name).toBe('AgentCardAuthError');
      expect(error.statusCode).toBe(401);
      expect(error.userMessage).toContain('401');
      expect(error.userMessage).toContain('Unauthorized');
      expect(error.userMessage).toContain('"auth" configuration');
    });

    it('should produce a user-friendly 403 message', () => {
      const error = new AgentCardAuthError(
        'secure-agent',
        'https://example.com/card',
        403,
      );
      expect(error.statusCode).toBe(403);
      expect(error.userMessage).toContain('403');
      expect(error.userMessage).toContain('Forbidden');
    });
  });

  describe('AgentAuthConfigMissingError', () => {
    it('should list missing config fields', () => {
      const error = new AgentAuthConfigMissingError(
        'api-agent',
        'API Key (x-api-key): Send x-api-key in header',
        [
          'Authentication is required but not configured',
          "Scheme 'api_key' requires apiKey authentication",
        ],
      );
      expect(error.name).toBe('AgentAuthConfigMissingError');
      expect(error.requiredAuth).toContain('API Key');
      expect(error.missingFields).toHaveLength(2);
      expect(error.userMessage).toContain('API Key');
      expect(error.userMessage).toContain('no auth is configured');
      expect(error.userMessage).toContain('Missing:');
    });
  });

  describe('AgentConnectionError', () => {
    it('should wrap the original error cause', () => {
      const cause = new Error('ECONNREFUSED');
      const error = new AgentConnectionError(
        'my-agent',
        'https://example.com/card',
        cause,
      );
      expect(error.name).toBe('AgentConnectionError');
      expect(error.userMessage).toContain('ECONNREFUSED');
      expect(error.userMessage).toContain('https://example.com/card');
    });

    it('should handle non-Error causes', () => {
      const error = new AgentConnectionError(
        'my-agent',
        'https://example.com/card',
        'raw string error',
      );
      expect(error.userMessage).toContain('raw string error');
    });
  });

  describe('classifyAgentError', () => {
    it('should classify a 404 error message', () => {
      const raw = new Error('HTTP 404: Not Found');
      const result = classifyAgentError(
        'agent-a',
        'https://example.com/card',
        raw,
      );
      expect(result).toBeInstanceOf(AgentCardNotFoundError);
      expect(result.agentName).toBe('agent-a');
    });

    it('should classify a "not found" error message (case-insensitive)', () => {
      const raw = new Error('Agent card not found at the given URL');
      const result = classifyAgentError(
        'agent-a',
        'https://example.com/card',
        raw,
      );
      expect(result).toBeInstanceOf(AgentCardNotFoundError);
    });

    it('should classify a 401 error message', () => {
      const raw = new Error('Request failed with status 401');
      const result = classifyAgentError(
        'agent-b',
        'https://example.com/card',
        raw,
      );
      expect(result).toBeInstanceOf(AgentCardAuthError);
      expect((result as AgentCardAuthError).statusCode).toBe(401);
    });

    it('should classify an "unauthorized" error message', () => {
      const raw = new Error('Unauthorized access to agent card');
      const result = classifyAgentError(
        'agent-b',
        'https://example.com/card',
        raw,
      );
      expect(result).toBeInstanceOf(AgentCardAuthError);
    });

    it('should classify a 403 error message', () => {
      const raw = new Error('HTTP 403 Forbidden');
      const result = classifyAgentError(
        'agent-c',
        'https://example.com/card',
        raw,
      );
      expect(result).toBeInstanceOf(AgentCardAuthError);
      expect((result as AgentCardAuthError).statusCode).toBe(403);
    });

    it('should fall back to AgentConnectionError for unknown errors', () => {
      const raw = new Error('Something completely unexpected');
      const result = classifyAgentError(
        'agent-d',
        'https://example.com/card',
        raw,
      );
      expect(result).toBeInstanceOf(AgentConnectionError);
    });

    it('should classify ECONNREFUSED as AgentConnectionError', () => {
      const raw = new Error('ECONNREFUSED 127.0.0.1:8080');
      const result = classifyAgentError(
        'agent-d',
        'https://example.com/card',
        raw,
      );
      expect(result).toBeInstanceOf(AgentConnectionError);
    });

    it('should handle non-Error values', () => {
      const result = classifyAgentError(
        'agent-e',
        'https://example.com/card',
        'some string error',
      );
      expect(result).toBeInstanceOf(AgentConnectionError);
    });

    describe('cause chain inspection', () => {
      it('should detect 404 in a nested cause', () => {
        const inner = new Error('HTTP 404 Not Found');
        const outer = new Error('fetch failed', { cause: inner });
        const result = classifyAgentError(
          'agent-nested',
          'https://example.com/card',
          outer,
        );
        expect(result).toBeInstanceOf(AgentCardNotFoundError);
      });

      it('should detect 401 in a deeply nested cause', () => {
        const innermost = new Error('Server returned 401');
        const middle = new Error('Request error', { cause: innermost });
        const outer = new Error('fetch failed', { cause: middle });
        const result = classifyAgentError(
          'agent-deep',
          'https://example.com/card',
          outer,
        );
        expect(result).toBeInstanceOf(AgentCardAuthError);
        expect((result as AgentCardAuthError).statusCode).toBe(401);
      });

      it('should detect ECONNREFUSED error code in cause chain', () => {
        const inner = Object.assign(new Error('connect failed'), {
          code: 'ECONNREFUSED',
        });
        const outer = new Error('fetch failed', { cause: inner });
        const result = classifyAgentError(
          'agent-conn',
          'https://example.com/card',
          outer,
        );
        expect(result).toBeInstanceOf(AgentConnectionError);
      });

      it('should detect status property on error objects in cause chain', () => {
        const inner = Object.assign(new Error('Bad response'), {
          status: 403,
        });
        const outer = new Error('agent card resolution failed', {
          cause: inner,
        });
        const result = classifyAgentError(
          'agent-status',
          'https://example.com/card',
          outer,
        );
        expect(result).toBeInstanceOf(AgentCardAuthError);
        expect((result as AgentCardAuthError).statusCode).toBe(403);
      });

      it('should detect status on a plain-object cause (non-Error)', () => {
        const outer = new Error('fetch failed');
        // Some HTTP libs set cause to a plain object, not an Error instance
        (outer as unknown as { cause: unknown }).cause = {
          message: 'Unauthorized',
          status: 401,
        };
        const result = classifyAgentError(
          'agent-plain-cause',
          'https://example.com/card',
          outer,
        );
        expect(result).toBeInstanceOf(AgentCardAuthError);
        expect((result as AgentCardAuthError).statusCode).toBe(401);
      });

      it('should detect statusCode on a plain-object cause (non-Error)', () => {
        const outer = new Error('fetch failed');
        (outer as unknown as { cause: unknown }).cause = {
          message: 'Forbidden',
          statusCode: 403,
        };
        const result = classifyAgentError(
          'agent-plain-cause-403',
          'https://example.com/card',
          outer,
        );
        expect(result).toBeInstanceOf(AgentCardAuthError);
        expect((result as AgentCardAuthError).statusCode).toBe(403);
      });

      it('should classify ENOTFOUND as AgentConnectionError, not 404', () => {
        // ENOTFOUND (DNS resolution failure) should NOT be misclassified
        // as a 404 despite containing "NOTFOUND" in the error code.
        const inner = Object.assign(
          new Error('getaddrinfo ENOTFOUND example.invalid'),
          {
            code: 'ENOTFOUND',
          },
        );
        const outer = new Error('fetch failed', { cause: inner });
        const result = classifyAgentError(
          'agent-dns',
          'https://example.invalid/card',
          outer,
        );
        expect(result).toBeInstanceOf(AgentConnectionError);
        expect(result).not.toBeInstanceOf(AgentCardNotFoundError);
      });
    });
  });
});
