import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';

import { HttpProvider } from './provider';
import {
  CreateInvoiceData,
  DataContainer,
  Invoice,
  InvoiceEnumStatus,
  InvoiceID,
  InvoiceStats,
  InvoiceUpdate,
} from '../../models/invoice';
import {
  CancelInvoiceResponseData,
  CreateInvoiceResponseData,
  GetInvoiceInfoResponseData,
  GetInvoiceStatsResponseData,
} from './types';

jest.mock('axios', () => {
  return {
    ...(jest.requireActual('axios') as object),
    create: jest.fn().mockReturnValue(jest.requireActual('axios')),
  };
});

const mockAdapter = new MockAdapter(axios);

describe('HttpProvider', () => {
  let httpProvider: HttpProvider;
  let axiosInstance: axios.AxiosInstance;

  beforeEach(() => {
    axiosInstance = axios.create({
      baseURL: 'http://example.com/',
    });
    httpProvider = new HttpProvider({ client: axiosInstance });
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('createInvoice', () => {
    it('should send a POST request with the correct data and return the response', async () => {
      const data: CreateInvoiceData = {
        order_id: 1,
        amount: 1,
      };

      const responseData: CreateInvoiceResponseData = {
        data: {
          id: '1',
          amount: '1',
          order_id: '1',
          status: InvoiceEnumStatus.created,
          createdAt: '2023-04-18T12:55:22.951Z',
          updatedAt: '2023-04-18T12:55:22.951Z',
        },
      };

      const expected: DataContainer<Invoice> = {
        data: {
          id: 1,
          amount: BigInt(1),
          order_id: BigInt(1),
          status: InvoiceEnumStatus.created,
          createdAt: Date.parse('2023-04-18T12:55:22.951Z'),
          updatedAt: Date.parse('2023-04-18T12:55:22.951Z'),
        },
        isValid: true,
      };

      mockAdapter.onPost('/invoice').reply(200, responseData);

      const invoice = await httpProvider.createInvoice(data);

      expect(invoice).toEqual(expected);
    });

    it('should handle HTTP error', async () => {
      const data: CreateInvoiceData = {
        order_id: 1,
        amount: 1,
      };

      const responseData = {};

      const expected: DataContainer<Invoice> = {
        data: null,
        isValid: false,
        error: expect.any(Error),
      };

      mockAdapter.onPost('/invoice').reply(500, responseData);

      const invoice = await httpProvider.createInvoice(data);

      expect(invoice).toEqual(expected);
    });

    it('should handle unexpected empty body', async () => {
      const data: CreateInvoiceData = {
        order_id: 1,
        amount: 1,
      };

      const expected: DataContainer<Invoice> = {
        data: null,
        isValid: false,
      };

      mockAdapter.onPost('/invoice').reply(200, null);

      const invoice = await httpProvider.createInvoice(data);

      expect(invoice).toEqual(expected);
    });
  });

  describe('cancelInvoice', () => {
    it('should send a PATCH request with the correct data and return the response', async () => {
      const invoiceID: InvoiceID = 1;

      const responseData: CancelInvoiceResponseData = {
        data: {
          id: 1,
          amount: 1,
          order_id: 1,
          status: InvoiceEnumStatus.canceled,
        },
      };

      const expected: DataContainer<Invoice> = {
        data: {
          id: 1,
          amount: BigInt(1),
          order_id: BigInt(1),
          status: InvoiceEnumStatus.canceled,
        },
        isValid: true,
      };

      mockAdapter.onPatch('/invoice/cancel').reply(200, responseData);

      const invoice = await httpProvider.cancelInvoice(invoiceID);

      expect(invoice).toEqual(expected);
    });

    it('should handle HTTP error', async () => {
      const invoiceID: InvoiceID = 1;

      const responseData = {};

      const expected: DataContainer<Invoice> = {
        data: null,
        isValid: false,
        error: expect.any(Error),
      };

      mockAdapter.onPatch('/invoice/cancel').reply(500, responseData);

      const invoice = await httpProvider.cancelInvoice(invoiceID);

      expect(invoice).toEqual(expected);
    });

    it('should handle unexpected empty body', async () => {
      const invoiceID: InvoiceID = 1;

      const expected: DataContainer<Invoice> = {
        data: null,
        isValid: false,
        error: expect.any(Error),
      };

      mockAdapter.onPatch('/invoice/cancel').reply(200, null);

      const invoice = await httpProvider.getInvoiceInfo(invoiceID);

      expect(invoice).toEqual(expected);
    });
  });

  describe('getInvoiceInfo', () => {
    it('should send a GET request with the correct data and return the response', async () => {
      const invoiceID: InvoiceID = 1;

      const responseData: GetInvoiceInfoResponseData = {
        data: {
          id: '1',
          amount: 1,
          order_id: '1',
          status: InvoiceEnumStatus.created,
          domain: 'abc.com',
          wallet_from_id: '12345',
        },
      };

      const expected: DataContainer<Invoice> = {
        data: {
          id: 1,
          amount: BigInt(1),
          order_id: BigInt(1),
          status: InvoiceEnumStatus.created,
          domain: 'abc.com',
          wallet_from_id: 12345,
        },
        isValid: true,
      };

      mockAdapter.onGet('/invoice/info?id=1').reply(200, responseData);

      const invoice = await httpProvider.getInvoiceInfo(invoiceID);

      expect(invoice).toEqual(expected);
    });

    it('should handle HTTP error', async () => {
      const invoiceID: InvoiceID = 1;

      const responseData = {};

      const expected: DataContainer<Invoice> = {
        data: null,
        isValid: false,
        error: expect.any(Error),
      };

      mockAdapter.onGet('/invoice/info?id=1').reply(404, responseData);

      const invoice = await httpProvider.getInvoiceInfo(invoiceID);

      expect(invoice).toEqual(expected);
    });

    it('should handle unexpected empty body', async () => {
      const invoiceID: InvoiceID = 1;

      const expected: DataContainer<Invoice> = {
        data: null,
        isValid: false,
      };

      mockAdapter.onGet('/invoice/info?id=1').reply(200, null);

      const invoice = await httpProvider.getInvoiceInfo(invoiceID);

      expect(invoice).toEqual(expected);
    });
  });

  describe('getInvoiceStats', () => {
    it('should send a GET request with the correct data and return the response', async () => {
      const responseData: GetInvoiceStatsResponseData = {
        data: {
          count: 1,
          invoice_sum: 10,
        },
      };

      const expected: DataContainer<InvoiceStats> = {
        data: {
          count: 1,
          invoice_sum: 10,
        },
        isValid: true,
      };

      mockAdapter.onGet('/invoice/stats').reply(200, responseData);

      const stats = await httpProvider.getInvoiceStats();

      expect(stats).toEqual(expected);
    });

    it('should handle HTTP error', async () => {
      const responseData: GetInvoiceStatsResponseData = {
        data: {
          count: 1,
          invoice_sum: 10,
        },
      };

      const expected: DataContainer<InvoiceStats> = {
        data: null,
        isValid: false,
        error: expect.any(Error),
      };

      mockAdapter.onGet('/invoice/stats').reply(403, responseData);

      const stats = await httpProvider.getInvoiceStats();

      expect(stats).toEqual(expected);
    });

    it('should handle unexpected empty body', async () => {
      const expected: DataContainer<InvoiceStats> = {
        data: null,
        isValid: false,
      };

      mockAdapter.onGet('/invoice/stats').reply(200, null);

      const stats = await httpProvider.getInvoiceStats();

      expect(stats).toEqual(expected);
    });
  });

  describe('isUpdateValid', () => {
    it('should return true for a valid update', () => {
      const update: InvoiceUpdate = {
        data: {
          status: InvoiceEnumStatus.canceled,
          code: '123456789',
          amount: 500,
          order_id: 12345,
          sign: '9403ff6d2edd7eede8a1172c3eb0dbab5a9a316c90fcafccea3a937618176580',
        },
      };

      const isValid = httpProvider.isUpdateValid(update);
      expect(isValid).toBe(true);
    });

    it('should return false for an invalid update', () => {
      const update: InvoiceUpdate = {
        data: {
          status: InvoiceEnumStatus.canceled,
          code: '123456789',
          amount: 500,
          order_id: 12345,
          sign: 'invalid_sign', // Replace with an invalid sign
        },
      };

      const isValid = httpProvider.isUpdateValid(update);
      expect(isValid).toBe(false);
    });
  });
});
