import { describe, test, expect, vi } from 'vitest';
import { 
  ErrorHelper, 
  ErrorCodes, 
  type ApiErrorResponse, 
  type AxiosErrorWithCode,
  type ErrorCode 
} from '../errors';

describe('ErrorHelper', () => {
  describe('getErrorInfo', () => {
    test('should handle structured API error response', () => {
      const structuredError = {
        response: {
          data: {
            error: 'User not found',
            code: 'USER_NOT_FOUND',
            status: 404
          },
          status: 404
        },
        isAxiosError: true
      };

      const result = ErrorHelper.getErrorInfo(structuredError);

      expect(result).toEqual({
        message: 'User not found',
        code: 'USER_NOT_FOUND',
        status: 404
      });
    });

    test('should handle legacy error format with message', () => {
      const legacyError = {
        response: {
          data: {
            message: 'Legacy error message'
          },
          status: 500
        },
        isAxiosError: true
      };

      const result = ErrorHelper.getErrorInfo(legacyError);

      expect(result).toEqual({
        message: 'Legacy error message',
        status: 500
      });
    });

    test('should handle axios errors without structured data', () => {
      const axiosError = {
        message: 'Network Error',
        response: {
          status: 500
        },
        isAxiosError: true
      };

      const result = ErrorHelper.getErrorInfo(axiosError);

      expect(result).toEqual({
        message: 'Network Error',
        status: 500
      });
    });

    test('should handle Error objects', () => {
      const error = new Error('Something went wrong');

      const result = ErrorHelper.getErrorInfo(error);

      expect(result).toEqual({
        message: 'Something went wrong'
      });
    });

    test('should handle string errors', () => {
      const result = ErrorHelper.getErrorInfo('Simple error string');

      expect(result).toEqual({
        message: 'An unknown error occurred'
      });
    });

    test('should handle null/undefined errors', () => {
      expect(ErrorHelper.getErrorInfo(null)).toEqual({
        message: 'An unknown error occurred'
      });

      expect(ErrorHelper.getErrorInfo(undefined)).toEqual({
        message: 'An unknown error occurred'
      });
    });

    test('should handle empty response data', () => {
      const emptyError = {
        response: {
          data: {},
          status: 400
        },
        message: 'Bad Request',
        isAxiosError: true
      };

      const result = ErrorHelper.getErrorInfo(emptyError);

      expect(result).toEqual({
        message: 'Bad Request',
        status: 400
      });
    });
  });

  describe('isErrorCode', () => {
    test('should return true for matching structured error code', () => {
      const error = {
        response: {
          data: {
            error: 'User not found',
            code: 'USER_NOT_FOUND',
            status: 404
          }
        },
        isAxiosError: true
      };

      expect(ErrorHelper.isErrorCode(error, ErrorCodes.USER_NOT_FOUND)).toBe(true);
    });

    test('should return false for non-matching structured error code', () => {
      const error = {
        response: {
          data: {
            error: 'User not found',
            code: 'USER_NOT_FOUND',
            status: 404
          }
        },
        isAxiosError: true
      };

      expect(ErrorHelper.isErrorCode(error, ErrorCodes.USER_DUPLICATE_USERNAME)).toBe(false);
    });

    test('should return false for legacy error without code', () => {
      const error = {
        response: {
          data: {
            message: 'Legacy error'
          }
        },
        isAxiosError: true
      };

      expect(ErrorHelper.isErrorCode(error, ErrorCodes.USER_NOT_FOUND)).toBe(false);
    });

    test('should return false for errors without response', () => {
      const error = new Error('Network Error');

      expect(ErrorHelper.isErrorCode(error, ErrorCodes.USER_NOT_FOUND)).toBe(false);
    });

    test('should handle null/undefined errors', () => {
      expect(ErrorHelper.isErrorCode(null, ErrorCodes.USER_NOT_FOUND)).toBe(false);
      expect(ErrorHelper.isErrorCode(undefined, ErrorCodes.USER_NOT_FOUND)).toBe(false);
    });
  });

  describe('getUserMessage', () => {
    test('should return error message for structured error', () => {
      const error = {
        response: {
          data: {
            error: 'Custom error message',
            code: 'USER_NOT_FOUND',
            status: 404
          }
        },
        isAxiosError: true
      };

      expect(ErrorHelper.getUserMessage(error)).toBe('Custom error message');
    });

    test('should return fallback message when provided', () => {
      const error = null;
      expect(ErrorHelper.getUserMessage(error, 'Custom fallback')).toBe('Custom fallback');
    });

    test('should return default fallback when no message', () => {
      const error = null;
      expect(ErrorHelper.getUserMessage(error)).toBe('An unknown error occurred');
    });
  });

  describe('getSuggestedAction', () => {
    test('should return specific action for user duplicate username', () => {
      const error = {
        response: {
          data: {
            error: 'Username already exists',
            code: 'USER_DUPLICATE_USERNAME',
            status: 409
          }
        },
        isAxiosError: true
      };

      expect(ErrorHelper.getSuggestedAction(error)).toBe('Please choose a different username');
    });

    test('should return specific action for invalid credentials', () => {
      const error = {
        response: {
          data: {
            error: 'Invalid login',
            code: 'USER_INVALID_CREDENTIALS',
            status: 401
          }
        },
        isAxiosError: true
      };

      expect(ErrorHelper.getSuggestedAction(error)).toBe('Please check your username and password');
    });

    test('should return null for unknown error codes', () => {
      const error = {
        response: {
          data: {
            error: 'Unknown error',
            code: 'UNKNOWN_ERROR_CODE',
            status: 500
          }
        },
        isAxiosError: true
      };

      expect(ErrorHelper.getSuggestedAction(error)).toBe(null);
    });

    test('should return null for errors without codes', () => {
      const error = new Error('Generic error');
      expect(ErrorHelper.getSuggestedAction(error)).toBe(null);
    });
  });

  describe('shouldShowRetry', () => {
    test('should return true for retryable error codes', () => {
      const error = {
        response: {
          data: {
            error: 'Connection failed',
            code: 'SOURCE_CONNECTION_FAILED',
            status: 503
          }
        },
        isAxiosError: true
      };

      expect(ErrorHelper.shouldShowRetry(error)).toBe(true);
    });

    test('should return true for 5xx server errors', () => {
      const error = {
        response: {
          data: {
            message: 'Internal server error'
          },
          status: 500
        },
        isAxiosError: true
      };

      expect(ErrorHelper.shouldShowRetry(error)).toBe(true);
    });

    test('should return false for client errors', () => {
      const error = {
        response: {
          data: {
            error: 'Bad request',
            code: 'USER_INVALID_CREDENTIALS',
            status: 400
          }
        },
        isAxiosError: true
      };

      expect(ErrorHelper.shouldShowRetry(error)).toBe(false);
    });
  });

  describe('getErrorCategory', () => {
    test('should categorize user auth errors correctly', () => {
      const authCodes = [
        'USER_INVALID_CREDENTIALS',
        'USER_TOKEN_EXPIRED', 
        'USER_SESSION_EXPIRED'
      ];

      authCodes.forEach(code => {
        const error = {
          response: {
            data: { error: 'Test', code, status: 401 }
          },
          isAxiosError: true
        };
        expect(ErrorHelper.getErrorCategory(error)).toBe('auth');
      });
    });

    test('should categorize user validation errors correctly', () => {
      const validationCodes = [
        'USER_INVALID_PASSWORD',
        'USER_INVALID_EMAIL',
        'USER_INVALID_USERNAME'
      ];

      validationCodes.forEach(code => {
        const error = {
          response: {
            data: { error: 'Test', code, status: 400 }
          },
          isAxiosError: true
        };
        expect(ErrorHelper.getErrorCategory(error)).toBe('validation');
      });
    });

    test('should categorize network errors correctly', () => {
      const error = {
        response: {
          data: {
            error: 'Connection failed',
            code: 'SOURCE_CONNECTION_FAILED',
            status: 503
          }
        },
        isAxiosError: true
      };

      expect(ErrorHelper.getErrorCategory(error)).toBe('network');
    });

    test('should categorize by HTTP status for errors without specific codes', () => {
      const statusTests = [
        { status: 400, expectedCategory: 'validation' },
        { status: 401, expectedCategory: 'auth' },
        { status: 403, expectedCategory: 'auth' },
        { status: 422, expectedCategory: 'validation' },
        { status: 500, expectedCategory: 'server' },
        { status: 502, expectedCategory: 'server' },
        { status: 503, expectedCategory: 'network' }
      ];

      statusTests.forEach(({ status, expectedCategory }) => {
        const error = {
          response: {
            data: { message: 'Test error' },
            status
          },
          isAxiosError: true
        };
        expect(ErrorHelper.getErrorCategory(error)).toBe(expectedCategory);
      });
    });

    test('should return unknown for unclassified errors', () => {
      const error = new Error('Generic error');
      expect(ErrorHelper.getErrorCategory(error)).toBe('unknown');
    });
  });

  describe('getErrorIcon', () => {
    test('should return appropriate icons for error categories', () => {
      const iconTests = [
        { category: 'auth', expectedIcon: '🔒' },
        { category: 'validation', expectedIcon: '⚠️' },
        { category: 'network', expectedIcon: '🌐' },
        { category: 'server', expectedIcon: '🔧' },
        { category: 'unknown', expectedIcon: '❌' }
      ];

      iconTests.forEach(({ category, expectedIcon }) => {
        // Create an error that will categorize to the desired category
        let error;
        switch (category) {
          case 'auth':
            error = { response: { data: { code: 'USER_INVALID_CREDENTIALS' }, status: 401 }, isAxiosError: true };
            break;
          case 'validation':
            error = { response: { data: { code: 'USER_INVALID_PASSWORD' }, status: 400 }, isAxiosError: true };
            break;
          case 'network':
            error = { response: { data: { code: 'SOURCE_CONNECTION_FAILED' }, status: 503 }, isAxiosError: true };
            break;
          case 'server':
            error = { response: { data: { message: 'Server error' }, status: 500 }, isAxiosError: true };
            break;
          default:
            error = new Error('Unknown error');
        }

        expect(ErrorHelper.getErrorIcon(error)).toBe(expectedIcon);
      });
    });
  });

  describe('formatErrorForDisplay', () => {
    test('should format error with actions included', () => {
      const error = {
        response: {
          data: {
            error: 'Username already exists',
            code: 'USER_DUPLICATE_USERNAME',
            status: 409
          }
        },
        isAxiosError: true
      };

      const result = ErrorHelper.formatErrorForDisplay(error, true);

      expect(result.message).toBe('Username already exists');
      expect(result.code).toBe('USER_DUPLICATE_USERNAME');
      expect(result.status).toBe(409);
      expect(result.suggestedAction).toBe('Please choose a different username');
      expect(result.category).toBe('validation');
      expect(result.icon).toBe('⚠️');
      expect(result.severity).toBe('warning');
      expect(typeof result.shouldShowRetry).toBe('boolean');
    });

    test('should format error without actions', () => {
      const error = {
        response: {
          data: {
            error: 'Username already exists',
            code: 'USER_DUPLICATE_USERNAME',
            status: 409
          }
        },
        isAxiosError: true
      };

      const result = ErrorHelper.formatErrorForDisplay(error, false);

      expect(result.message).toBe('Username already exists');
      expect(result.suggestedAction).toBe(null);
      expect(result.shouldShowRetry).toBe(false);
    });

    test('should set correct severity based on category', () => {
      const severityTests = [
        { code: 'USER_INVALID_PASSWORD', expectedSeverity: 'warning' }, // validation
        { code: 'USER_INVALID_CREDENTIALS', expectedSeverity: 'info' }, // auth
        { code: 'SOURCE_CONNECTION_FAILED', expectedSeverity: 'error' } // network
      ];

      severityTests.forEach(({ code, expectedSeverity }) => {
        const error = {
          response: {
            data: { error: 'Test', code, status: 400 }
          },
          isAxiosError: true
        };
        
        const result = ErrorHelper.formatErrorForDisplay(error, true);
        expect(result.severity).toBe(expectedSeverity);
      });
    });
  });

  describe('handleSpecificError', () => {
    test('should handle session expired errors with login callback', () => {
      const error = {
        response: {
          data: {
            error: 'Session expired',
            code: 'USER_SESSION_EXPIRED',
            status: 401
          }
        },
        isAxiosError: true
      };

      const onLogin = vi.fn();
      const result = ErrorHelper.handleSpecificError(error, undefined, onLogin);

      expect(result).toBe(true);
      expect(onLogin).toHaveBeenCalled();
    });

    test('should handle retryable errors with retry callback', async () => {
      const error = {
        response: {
          data: {
            error: 'Connection failed',
            code: 'SOURCE_CONNECTION_FAILED',
            status: 503
          }
        },
        isAxiosError: true
      };

      const onRetry = vi.fn();
      const result = ErrorHelper.handleSpecificError(error, onRetry);

      expect(result).toBe(true);
      
      // Wait for the timeout to trigger
      await new Promise(resolve => setTimeout(resolve, 2100));
      expect(onRetry).toHaveBeenCalled();
    });

    test('should not handle non-specific errors', () => {
      const error = {
        response: {
          data: {
            error: 'Generic error',
            code: 'SOME_OTHER_ERROR',
            status: 400
          }
        },
        isAxiosError: true
      };

      const result = ErrorHelper.handleSpecificError(error);
      expect(result).toBe(false);
    });

    test('should not handle session expired without login callback', () => {
      const error = {
        response: {
          data: {
            error: 'Session expired',
            code: 'USER_SESSION_EXPIRED',
            status: 401
          }
        },
        isAxiosError: true
      };

      const result = ErrorHelper.handleSpecificError(error);
      expect(result).toBe(false);
    });
  });

  describe('ErrorCodes constants', () => {
    test('should have all required user error codes', () => {
      expect(ErrorCodes.USER_NOT_FOUND).toBe('USER_NOT_FOUND');
      expect(ErrorCodes.USER_DUPLICATE_USERNAME).toBe('USER_DUPLICATE_USERNAME');
      expect(ErrorCodes.USER_DUPLICATE_EMAIL).toBe('USER_DUPLICATE_EMAIL');
      expect(ErrorCodes.USER_INVALID_CREDENTIALS).toBe('USER_INVALID_CREDENTIALS');
      expect(ErrorCodes.USER_SESSION_EXPIRED).toBe('USER_SESSION_EXPIRED');
      expect(ErrorCodes.USER_TOKEN_EXPIRED).toBe('USER_TOKEN_EXPIRED');
      expect(ErrorCodes.USER_PERMISSION_DENIED).toBe('USER_PERMISSION_DENIED');
      expect(ErrorCodes.USER_ACCOUNT_DISABLED).toBe('USER_ACCOUNT_DISABLED');
    });

    test('should have all required source error codes', () => {
      expect(ErrorCodes.SOURCE_NOT_FOUND).toBe('SOURCE_NOT_FOUND');
      expect(ErrorCodes.SOURCE_CONNECTION_FAILED).toBe('SOURCE_CONNECTION_FAILED');
      expect(ErrorCodes.SOURCE_AUTH_FAILED).toBe('SOURCE_AUTH_FAILED');
      expect(ErrorCodes.SOURCE_CONFIG_INVALID).toBe('SOURCE_CONFIG_INVALID');
      expect(ErrorCodes.SOURCE_SYNC_IN_PROGRESS).toBe('SOURCE_SYNC_IN_PROGRESS');
    });

    test('should have all required label error codes', () => {
      expect(ErrorCodes.LABEL_NOT_FOUND).toBe('LABEL_NOT_FOUND');
      expect(ErrorCodes.LABEL_DUPLICATE_NAME).toBe('LABEL_DUPLICATE_NAME');
      expect(ErrorCodes.LABEL_INVALID_NAME).toBe('LABEL_INVALID_NAME');
      expect(ErrorCodes.LABEL_INVALID_COLOR).toBe('LABEL_INVALID_COLOR');
      expect(ErrorCodes.LABEL_IN_USE).toBe('LABEL_IN_USE');
      expect(ErrorCodes.LABEL_SYSTEM_MODIFICATION).toBe('LABEL_SYSTEM_MODIFICATION');
      expect(ErrorCodes.LABEL_MAX_LABELS_REACHED).toBe('LABEL_MAX_LABELS_REACHED');
    });
  });

  describe('Edge cases and robustness', () => {
    test('should handle malformed error objects', () => {
      const malformedErrors = [
        { response: null, isAxiosError: true },
        { response: { data: null }, isAxiosError: true },
        { response: { data: { error: null, code: null } }, isAxiosError: true },
        { response: { status: 'not-a-number' }, isAxiosError: true },
        { code: 123, isAxiosError: true }, // numeric code instead of string
        { message: { nested: 'object' }, isAxiosError: true } // object instead of string
      ];

      malformedErrors.forEach(error => {
        expect(() => ErrorHelper.getErrorInfo(error)).not.toThrow();
        expect(() => ErrorHelper.formatErrorForDisplay(error, true)).not.toThrow();
        expect(() => ErrorHelper.isErrorCode(error, ErrorCodes.USER_NOT_FOUND)).not.toThrow();
        expect(() => ErrorHelper.getErrorCategory(error)).not.toThrow();
        expect(() => ErrorHelper.getErrorIcon(error)).not.toThrow();
      });
    });

    test('should handle very long error messages', () => {
      const longMessage = 'x'.repeat(10000);
      const error = {
        response: {
          data: {
            error: longMessage,
            code: 'USER_NOT_FOUND',
            status: 404
          }
        },
        isAxiosError: true
      };

      const result = ErrorHelper.getErrorInfo(error);
      expect(result.message).toBe(longMessage);
      expect(result.code).toBe('USER_NOT_FOUND');
    });

    test('should handle non-string error codes gracefully', () => {
      const error = {
        response: {
          data: {
            error: 'Test error',
            code: 12345, // numeric code
            status: 400
          }
        },
        isAxiosError: true
      };

      // Should not crash and should handle it as if no code was provided
      expect(() => ErrorHelper.getErrorInfo(error)).not.toThrow();
      expect(() => ErrorHelper.isErrorCode(error, ErrorCodes.USER_NOT_FOUND)).not.toThrow();
    });
  });

  describe('Type safety', () => {
    test('should handle ApiErrorResponse interface correctly', () => {
      const apiError: ApiErrorResponse = {
        error: 'Test error message',
        code: 'USER_NOT_FOUND',
        status: 404
      };

      const error = {
        response: {
          data: apiError,
          status: 404
        },
        isAxiosError: true
      };

      const result = ErrorHelper.getErrorInfo(error);
      expect(result.message).toBe('Test error message');
      expect(result.code).toBe('USER_NOT_FOUND');
      expect(result.status).toBe(404);
    });

    test('should handle AxiosErrorWithCode interface correctly', () => {
      const axiosError: AxiosErrorWithCode = {
        response: {
          data: {
            error: 'Axios error',
            code: 'SOURCE_CONNECTION_FAILED',
            status: 503
          },
          status: 503,
          statusText: 'Service Unavailable',
          headers: {}
        },
        message: 'Request failed',
        name: 'AxiosError',
        isAxiosError: true
      };

      const result = ErrorHelper.getErrorInfo(axiosError);
      expect(result.message).toBe('Axios error');
      expect(result.code).toBe('SOURCE_CONNECTION_FAILED');
      expect(result.status).toBe(503);
    });
  });
});