/**
 * Copyright IBM Corp. 2024, 2025
 */
import {
  addErrorToResponse,
  errorsArray,
  constructErrorResponse,
  filterSensitiveData,
  generateCSV,
  generatePDF,
  sanitizeAxiosResponse,
  isSuccessStatus,
  parseSetCookies,
} from '../../src/helpers/helper.js';
import { Buffer } from 'buffer';

jest.mock('../../src/service/log-wrapper.ts');

describe('addErrorToResponse', () => {
  beforeEach(() => {
    // Clear errorsArray before each test
    errorsArray.length = 0;
  });

  test('should add error object to errorsArray', () => {
    addErrorToResponse('ERR001', 'field1', 'Error description 1');
    expect(errorsArray).toHaveLength(1);
    expect(errorsArray[0]).toEqual({
      code: 'ERR001',
      field: 'field1',
      description: 'Error description 1',
    });
  });

  test('should add multiple error objects to errorsArray', () => {
    addErrorToResponse('ERR001', 'field1', 'Error description 1');
    addErrorToResponse('ERR002', 'field2', 'Error description 2');
    expect(errorsArray).toHaveLength(2);
    expect(errorsArray[0]).toEqual({
      code: 'ERR001',
      field: 'field1',
      description: 'Error description 1',
    });
    expect(errorsArray[1]).toEqual({
      code: 'ERR002',
      field: 'field2',
      description: 'Error description 2',
    });
  });
});
describe('constructErrorResponse', () => {
  beforeEach(() => {
    // Clear errorsArray before each test
    errorsArray.length = 0;
  });

  test('should construct error response object with correct properties', () => {
    addErrorToResponse('ERR001', 'field1', 'Error description 1');
    addErrorToResponse('ERR002', 'field2', 'Error description 2');

    const response = constructErrorResponse();

    expect(response.respCode).toBe(400);
    expect(response.message).toBe('Invalid Assets or Reference in the Zip');
    expect(response.Endpoints).toEqual([]);
    expect(response.errors).toHaveLength(2);
    expect(response.errors[0]).toEqual({
      code: 'ERR001',
      field: 'field1',
      description: 'Error description 1',
    });
    expect(response.errors[1]).toEqual({
      code: 'ERR002',
      field: 'field2',
      description: 'Error description 2',
    });
  });

  test('should clear errorsArray after constructing error response', () => {
    addErrorToResponse('ERR001', 'field1', 'Error description 1');

    constructErrorResponse();

    expect(errorsArray).toHaveLength(0);
  });
});

describe('filterSensitiveData', () => {
  it('should remove exact sensitive keys like password and token', () => {
    const input = {
      username: 'user1',
      contact: {
        username: 'user3',
        password: 'secret123', // pragma: allowlist secret
      },
      token: 'abcd1234',
    };
    const result = filterSensitiveData(input);
    expect(result).toEqual({
      username: 'user1',
      contact: { username: 'user3' },
    });
  });

  it('should remove keys that match sensitive patterns like SECRET and sensitive', () => {
    const input = {
      API_SECRET: 'hidden', // pragma: allowlist secret
      sensitive_info: 'classified',
      userId: 42,
    };
    const result = filterSensitiveData(input);
    expect(result).toEqual({ userId: 42 });
  });

  it('should remove case-insensitive matches', () => {
    const input = {
      PASSWORD: 'pass',
      Token: '123',
      SecretKey: 'topsecret',
      EnvVar: 'do-not-export',
    };
    const result = filterSensitiveData(input);
    expect(result).toEqual({});
  });

  it('should retain non-sensitive fields', () => {
    const input = {
      name: 'Alice',
      email: 'alice@example.com',
    };
    const result = filterSensitiveData(input);
    expect(result).toEqual(input);
  });

  it('should handle an empty input object', () => {
    const result = filterSensitiveData({});
    expect(result).toEqual({});
  });
});

describe('generateCSV', () => {
  const testData = [
    { id: '1', name: 'John Doe', age: 30 },
    { id: '2', name: 'Jane Doe', age: 28 },
  ];

  it('should generate a CSV buffer with the correct header and data', () => {
    const csvBuffer = generateCSV(testData);

    expect(csvBuffer.toString()).toBe(
      'id,name,age\n1,John Doe,30\n2,Jane Doe,28\n',
    );
  });

  it('should handle empty data array', () => {
    const csvBuffer = generateCSV([]);

    expect(csvBuffer.toString('utf-8')).toBe('');
  });

  it('should handle objects with varying field order by using the first object’s keys as headers', () => {
    const data = [
      { firstName: 'Carol', lastName: 'Danvers', score: 95 },
      // Second object has keys shuffled
      Object.assign({}, { score: 82, lastName: 'Frost', firstName: 'Dave' }),
    ];

    const buffer: Buffer = generateCSV(data);
    const csv = buffer.toString('utf-8');
    const lines = csv.trim().split('\n');

    expect(lines[0]).toBe('firstName,lastName,score');
    expect(lines[1]).toBe('Carol,Danvers,95');
    expect(lines[2]).toBe('Dave,Frost,82');
  });
});

describe('generatePDF', () => {
  it('should return a non-empty Buffer', async () => {
    const data = [{ name: 'Test', value: 123 }];
    const buffer = await generatePDF(data);
    expect(buffer.length).toBeGreaterThan(0);
  });
});

describe('sanitizeAxiosResponse', () => {
  it('should handle successful response', () => {
    const mockResponse = {
      status: 200,
      statusText: 'OK',
      headers: [
        { key: 'content-type', value: 'application/json' },
        { key: 'set-cookie', value: ['session=123; Path=/; HttpOnly'] },
      ],
      config: {
        method: 'get',
        url: 'https://api.example.com/data',
        timeout: 5000,
      },
      data: { id: 1, name: 'Test' },
      responseTime: 250,
    };

    const result = sanitizeAxiosResponse(mockResponse);

    expect(result).toEqual({
      success: true,
      status: 200,
      statusText: 'OK',
      headers: mockResponse.headers,
      cookies: [{ key: 'session', value: '123' }],
      config: {
        method: 'get',
        url: 'https://api.example.com/data',
        timeout: 5000,
      },
      data: { id: 1, name: 'Test' },
      responseTime: 250,
    });
  });

  it('should handle error response', () => {
    const mockError = {
      message: 'Request failed with status code 404',
      code: 'ERR_BAD_REQUEST',
      response: {
        status: 404,
        statusText: 'Not Found',
        headers: [
          { key: 'content-type', value: 'application/json' },
          { key: 'set-cookie', value: ['session=xyz; Path=/; HttpOnly'] },
        ],
        data: { error: 'Resource not found' },
      },
      config: {
        method: 'get',
        url: 'https://api.example.com/missing',
        timeout: 3000,
      },
    };

    const result = sanitizeAxiosResponse(null, mockError);

    expect(result).toEqual({
      success: false,
      message: 'Request failed with status code 404',
      code: 'ERR_BAD_REQUEST',
      status: 404,
      statusText: 'Not Found',
      headers: mockError.response.headers,
      cookies: [{ key: 'session', value: 'xyz' }],
      config: {
        method: 'get',
        url: 'https://api.example.com/missing',
        timeout: 3000,
      },
      data: { error: 'Resource not found' },
    });
  });

  it('should handle error response with missing response object', () => {
    const mockError = {
      message: 'Network Error',
      code: 'ERR_NETWORK',
      config: {
        method: 'get',
        url: 'https://api.example.com/data',
        timeout: 3000,
      },
    };

    const result = sanitizeAxiosResponse(null, mockError);

    expect(result).toEqual({
      success: false,
      message: 'Network Error',
      code: 'ERR_NETWORK',
      status: null,
      statusText: null,
      headers: {},
      cookies: [],
      config: {
        method: 'get',
        url: 'https://api.example.com/data',
        timeout: 3000,
      },
      data: null,
    });
  });

  it('should handle error response with missing headers', () => {
    const mockError = {
      message: 'Request failed',
      code: 'ERR_BAD_REQUEST',
      response: {
        status: 500,
        statusText: 'Internal Server Error',
        headers: [],
        data: { message: 'Server error' },
      },
      config: {
        method: 'post',
        url: 'https://api.example.com/data',
      },
    };

    const result = sanitizeAxiosResponse(null, mockError);

    expect(result).toEqual({
      success: false,
      message: 'Request failed',
      code: 'ERR_BAD_REQUEST',
      status: 500,
      statusText: 'Internal Server Error',
      headers: [],
      cookies: [],
      config: {
        method: 'post',
        url: 'https://api.example.com/data',
        timeout: undefined,
      },
      data: { message: 'Server error' },
    });
  });

  it('should handle error response with missing config', () => {
    const mockError = {
      message: 'Request failed',
      code: 'ERR_BAD_REQUEST',
      response: {
        status: 400,
        statusText: 'Bad Request',
        headers: [{ key: 'content-type', value: 'application/json' }],
        data: { message: 'Invalid input' },
      },
    };

    const result = sanitizeAxiosResponse(null, mockError);

    expect(result).toEqual({
      success: false,
      message: 'Request failed',
      code: 'ERR_BAD_REQUEST',
      status: 400,
      statusText: 'Bad Request',
      headers: mockError.response.headers,
      cookies: [],
      config: {
        method: undefined,
        url: undefined,
        timeout: undefined,
      },
      data: { message: 'Invalid input' },
    });
  });
});

describe('isSuccessStatus', () => {
  it('should return true for status codes in the 2xx range', () => {
    expect(isSuccessStatus(200)).toBe(true);
    expect(isSuccessStatus(201)).toBe(true);
  });

  it('should return false for status codes outside the 2xx range', () => {
    // 4xx status codes
    expect(isSuccessStatus(400)).toBe(false);
    expect(isSuccessStatus(404)).toBe(false);

    // 5xx status codes
    expect(isSuccessStatus(500)).toBe(false);
    expect(isSuccessStatus(503)).toBe(false);
  });
});

describe('parseSetCookies', () => {
  // Test case 1: Handling empty, null, or undefined input
  describe('handling empty or invalid inputs', () => {
    it('should return an empty array for empty string', () => {
      expect(parseSetCookies('')).toEqual([]);
    });

    it('should handle null-like values correctly', () => {
      // @ts-ignore - Testing runtime behavior with null
      expect(parseSetCookies(null)).toEqual([]);

      // @ts-ignore - Testing runtime behavior with undefined
      expect(parseSetCookies(undefined)).toEqual([]);
    });

    it('should return an empty array for whitespace string', () => {
      expect(parseSetCookies('   ')).toEqual([]);
    });

    it('should return an empty array for empty array', () => {
      expect(parseSetCookies([])).toEqual([]);
    });
  });

  // Test case 2: Parsing a single Set-Cookie header
  describe('parsing a single Set-Cookie header', () => {
    it('should parse a simple name=value cookie', () => {
      const result = parseSetCookies('session=abc123');
      expect(result).toEqual([{ key: 'session', value: 'abc123' }]);
    });

    it('should parse a cookie with attributes', () => {
      const result = parseSetCookies('session=abc123; Path=/; HttpOnly');
      expect(result).toEqual([{ key: 'session', value: 'abc123' }]);
    });

    it('should handle cookies with equals sign in the value', () => {
      const result = parseSetCookies('data=key1=value1&key2=value2; Path=/');
      expect(result).toEqual([
        { key: 'data', value: 'key1=value1&key2=value2' },
      ]);
    });

    it('should trim whitespace from cookie name and value', () => {
      const result = parseSetCookies('  session  =  abc123  ; Path=/');
      expect(result).toEqual([{ key: 'session', value: 'abc123' }]);
    });
  });

  // Test case 3: Parsing multiple Set-Cookie headers
  describe('parsing multiple Set-Cookie headers', () => {
    it('should parse an array of cookie strings', () => {
      const cookies = [
        'session=abc123; Path=/; HttpOnly',
        'user=john; Path=/account; Secure',
        'theme=dark',
      ];

      const result = parseSetCookies(cookies);

      expect(result).toEqual([
        { key: 'session', value: 'abc123' },
        { key: 'user', value: 'john' },
        { key: 'theme', value: 'dark' },
      ]);
    });

    it('should handle empty strings in the array', () => {
      const cookies = ['session=abc123', '', 'theme=dark'];

      const result = parseSetCookies(cookies);

      expect(result).toEqual([
        { key: 'session', value: 'abc123' },
        { key: '', value: '' },
        { key: 'theme', value: 'dark' },
      ]);
    });
  });

  // Test case 4: Handling various cookie attributes
  describe('handling various cookie attributes', () => {
    it('should extract only the cookie name-value pair, ignoring attributes', () => {
      const cookie =
        'session=abc123; Expires=Wed, 21 Oct 2025 07:28:00 GMT; Max-Age=3600; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=Strict';

      const result = parseSetCookies(cookie);

      expect(result).toEqual([{ key: 'session', value: 'abc123' }]);
    });

    it('should handle cookies with multiple attributes in different orders', () => {
      const cookies = [
        'session=abc123; HttpOnly; Path=/; Secure',
        'user=john; SameSite=Lax; Domain=example.com',
      ];

      const result = parseSetCookies(cookies);

      expect(result).toEqual([
        { key: 'session', value: 'abc123' },
        { key: 'user', value: 'john' },
      ]);
    });
  });

  // Test case 5: Edge cases and special characters
  describe('handling edge cases and special characters', () => {
    it('should handle cookies with special characters in values', () => {
      const cookies = [
        'complex=value@with+special&chars?',
        'json={"key":"value"}',
        'encoded=hello%20world',
      ];

      const result = parseSetCookies(cookies);

      expect(result).toEqual([
        { key: 'complex', value: 'value@with+special&chars?' },
        { key: 'json', value: '{"key":"value"}' },
        { key: 'encoded', value: 'hello%20world' },
      ]);
    });

    it('should handle malformed cookies missing values', () => {
      const cookies = ['session=', 'user', '=value'];

      const result = parseSetCookies(cookies);

      expect(result).toEqual([
        { key: 'session', value: '' },
        { key: 'user', value: '' },
        { key: '', value: 'value' },
      ]);
    });

    it('should handle cookies with multiple equal signs', () => {
      const result = parseSetCookies('token=abc=123=xyz; Path=/');
      expect(result).toEqual([{ key: 'token', value: 'abc=123=xyz' }]);
    });
  });

  // Test case 6: Verifying correct object structure
  describe('verifying correct object structure', () => {
    it('should always return an array of objects with key and value properties', () => {
      const result = parseSetCookies('session=abc123');

      expect(Array.isArray(result)).toBe(true);
      expect(result[0]).toHaveProperty('key');
      expect(result[0]).toHaveProperty('value');
      expect(Object.keys(result[0]).length).toBe(2); // Only key and value properties
    });

    it('should maintain the order of cookies as provided in the input', () => {
      const cookies = ['first=cookie', 'second=cookie', 'third=cookie'];

      const result = parseSetCookies(cookies);

      expect(result[0].key).toBe('first');
      expect(result[1].key).toBe('second');
      expect(result[2].key).toBe('third');
    });
  });
});
