import sendRequest, { createSendRequest } from './send-request';

jest.mock('../network', () => ({
  makeAxiosInstance: jest.fn()
}));

jest.mock('../utils/http-https-agents', () => ({
  getHttpHttpsAgents: jest.fn()
}));

import { makeAxiosInstance } from '../network';
import { getHttpHttpsAgents } from '../utils/http-https-agents';

const mockMakeAxiosInstance = makeAxiosInstance as jest.Mock;
const mockGetHttpHttpsAgents = getHttpHttpsAgents as jest.Mock;

describe('sendRequest', () => {
  let mockAxios: jest.Mock;

  beforeEach(() => {
    jest.clearAllMocks();
    mockAxios = jest.fn();
    mockMakeAxiosInstance.mockReturnValue(mockAxios);
    mockGetHttpHttpsAgents.mockResolvedValue({ httpAgent: null, httpsAgent: null });
  });

  describe('without callback', () => {
    test('should return response directly', async () => {
      const mockResponse = { data: 'test', status: 200 };
      mockAxios.mockResolvedValue(mockResponse);

      const result = await sendRequest({ url: 'http://example.com' });

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

    test('should reject on request error', async () => {
      const error = new Error('Network error');
      mockAxios.mockRejectedValue(error);

      await expect(sendRequest({ url: 'http://example.com' })).rejects.toThrow('Network error');
    });

    test('should handle URL string instead of config object', async () => {
      const mockResponse = { data: 'pong', status: 200 };
      mockAxios.mockResolvedValue(mockResponse);

      const result = await sendRequest('http://example.com/ping');

      expect(result).toBe(mockResponse);
      expect(mockAxios).toHaveBeenCalledWith(
        expect.objectContaining({
          url: 'http://example.com/ping'
        })
      );
    });
  });

  describe('with callback', () => {
    test('should call callback with response and return response', async () => {
      const mockResponse = { data: 'test', status: 200 };
      mockAxios.mockResolvedValue(mockResponse);
      const callback = jest.fn();

      const result = await sendRequest({ url: 'http://example.com' }, callback);

      expect(callback).toHaveBeenCalledWith(null, mockResponse);
      expect(result).toBe(mockResponse);
    });

    test('should call callback with error on request failure', async () => {
      const error = new Error('Network error');
      mockAxios.mockRejectedValue(error);
      const callback = jest.fn();

      await sendRequest({ url: 'http://example.com' }, callback);

      expect(callback).toHaveBeenCalledWith(error, null);
    });

    test('should reject if callback throws on success', async () => {
      const mockResponse = { data: 'test', status: 200 };
      mockAxios.mockResolvedValue(mockResponse);
      const callbackError = new Error('Callback error');
      const callback = jest.fn().mockRejectedValue(callbackError);

      await expect(sendRequest({ url: 'http://example.com' }, callback)).rejects.toThrow(
        'Callback error'
      );
    });

    test('should reject if callback throws on error', async () => {
      const requestError = new Error('Network error');
      mockAxios.mockRejectedValue(requestError);
      const callbackError = new Error('Callback error');
      const callback = jest.fn().mockRejectedValue(callbackError);

      await expect(sendRequest({ url: 'http://example.com' }, callback)).rejects.toThrow(
        'Callback error'
      );
    });
  });
});

describe('createSendRequest', () => {
  let mockAxios: jest.Mock;

  beforeEach(() => {
    jest.clearAllMocks();
    mockAxios = jest.fn();
    mockMakeAxiosInstance.mockReturnValue(mockAxios);
  });

  test('should apply agents from config', async () => {
    const mockHttpAgent = { name: 'httpAgent' };
    const mockHttpsAgent = { name: 'httpsAgent' };
    mockGetHttpHttpsAgents.mockResolvedValue({
      httpAgent: mockHttpAgent,
      httpsAgent: mockHttpsAgent
    });
    const mockResponse = { data: 'test' };
    mockAxios.mockResolvedValue(mockResponse);

    const customSendRequest = createSendRequest({ proxyConfig: {} });
    await customSendRequest({ url: 'https://example.com' });

    expect(mockGetHttpHttpsAgents).toHaveBeenCalledWith({
      proxyConfig: {},
      requestUrl: 'https://example.com'
    });
    expect(mockAxios).toHaveBeenCalledWith(
      expect.objectContaining({
        httpAgent: mockHttpAgent,
        httpsAgent: mockHttpsAgent
      })
    );
  });

  test('should not override agents if already set in requestConfig', async () => {
    const configHttpAgent = { name: 'configAgent' };
    const configHttpsAgent = { name: 'configHttpsAgent' };
    mockGetHttpHttpsAgents.mockResolvedValue({
      httpAgent: { name: 'ignored' },
      httpsAgent: { name: 'ignored' }
    });
    mockAxios.mockResolvedValue({ data: 'test' });

    const customSendRequest = createSendRequest({ proxyConfig: {} });
    await customSendRequest({
      url: 'https://example.com',
      httpAgent: configHttpAgent,
      httpsAgent: configHttpsAgent
    });

    expect(mockAxios).toHaveBeenCalledWith(
      expect.objectContaining({
        httpAgent: configHttpAgent,
        httpsAgent: configHttpsAgent
      })
    );
  });

  test('should not call getHttpHttpsAgents when no config provided', async () => {
    mockAxios.mockResolvedValue({ data: 'test' });

    const customSendRequest = createSendRequest();
    await customSendRequest({ url: 'https://example.com' });

    expect(mockGetHttpHttpsAgents).not.toHaveBeenCalled();
  });

  test('should handle URL string and apply agents from config', async () => {
    const mockHttpAgent = { name: 'httpAgent' };
    const mockHttpsAgent = { name: 'httpsAgent' };
    mockGetHttpHttpsAgents.mockResolvedValue({
      httpAgent: mockHttpAgent,
      httpsAgent: mockHttpsAgent
    });
    const mockResponse = { data: 'pong' };
    mockAxios.mockResolvedValue(mockResponse);

    const customSendRequest = createSendRequest({ collectionPath: '/test' });
    const result = await customSendRequest('https://example.com/ping');

    expect(result).toBe(mockResponse);
    expect(mockGetHttpHttpsAgents).toHaveBeenCalledWith({
      collectionPath: '/test',
      requestUrl: 'https://example.com/ping'
    });
    expect(mockAxios).toHaveBeenCalledWith(
      expect.objectContaining({
        url: 'https://example.com/ping',
        httpAgent: mockHttpAgent,
        httpsAgent: mockHttpsAgent
      })
    );
  });
});
