/**
 * @jest-environment jsdom
 */
import CfCore from "../../../core/CfCore";
import ECommerce from "..";

import {
  ECommerceTypes,
  ItemType,
  StockStatus,
  ItemProperties,
  ItemAction,
} from "../typings";

import { CurrencyCode } from "../../../core/commonTypes";
import { ContentBlock } from "../../Navigation/typings";

let windowSpy;
let mockSender;
let mockImpressionManager = {
  on: () => {},
};

const device_id = "asdf";
describe("ECommerce", () => {
  beforeEach(() => {
    mockSender = {
      add() {
        return false;
      },
      sendException() {
        return false;
      },
    };
    windowSpy = jest.spyOn(window, "window", "get");
  });

  afterEach(() => {
    // to remove the singleton instance
    (CfCore as any).instance = null;
  });

  it('Should deliver a "Item" event', async () => {
    const senderSpy = jest.spyOn(mockSender, "add");

    const cfCore = CfCore.createInstance(
      mockSender as any,
      mockImpressionManager as any,
      false,
      ContentBlock.Core,
      device_id,
      false
    );

    const event: ItemProperties = {
      action: ItemAction.View,
      item: {
        id: "111",
        price: 90,
        type: ItemType.Drug,
        quantity: 1,
        promo_id: "11111",
        currency: CurrencyCode.EUR,
        stock_status: StockStatus.InStock,
      },
    };

    await ECommerce.logIngestEvent(ECommerceTypes.Item, event, true);

    expect(senderSpy).toHaveBeenCalledWith(
      expect.stringMatching(device_id),
      expect.stringMatching(device_id),
      expect.objectContaining({
        name: ECommerceTypes.Item,
        ctx: expect.objectContaining({
          // El item se transforma por getPayloadFromItem y serializeToFlatPrimitiveMap
          drug_id: "111", // Se agrega dinámicamente basado en el type
          price: 90,
          type: ItemType.Drug,
          quantity: 1,
          promo_id: "11111",
          currency: CurrencyCode.EUR,
          stock_status: StockStatus.InStock,
        }),
      }),
      true,
      expect.anything()
    );
  });

  it("Should throw exception and call sendException when quantity is negative", async () => {
    const addSpy = jest.spyOn(mockSender, "add");
    const sendExceptionSpy = jest.spyOn(mockSender, "sendException");

    const cfCore = CfCore.createInstance(
      mockSender as any,
      mockImpressionManager as any,
      false,
      ContentBlock.Core,
      device_id,
      false
    );

    const event: ItemProperties = {
      action: ItemAction.View,
      item: {
        id: "111",
        price: 90,
        type: ItemType.Drug,
        quantity: -1, // Negative quantity
        promo_id: "11111",
        currency: CurrencyCode.EUR,
        stock_status: StockStatus.InStock,
      },
    };

    // The function should throw an exception due to negative quantity
    await expect(
      ECommerce.logIngestEvent(ECommerceTypes.Item, event, true)
    ).rejects.toThrow();

    // add() should be called with an exception event when validation fails
    expect(addSpy).toHaveBeenCalledWith(
      expect.stringMatching(device_id),
      expect.stringMatching(device_id),
      expect.objectContaining({
        name: "exception",
        property: "item",
        ctx: expect.objectContaining({
          event_type: "item",
          exception_type: "Error",
          exception_source: "JS-SDK",
        }),
      }),
      false,
      false
    );

    // sendException() should not be called (exceptions are logged via add)
    expect(sendExceptionSpy).not.toHaveBeenCalled();
  });

  it("Should throw exception and call sendException when price is negative", async () => {
    const addSpy = jest.spyOn(mockSender, "add");
    const sendExceptionSpy = jest.spyOn(mockSender, "sendException");

    const cfCore = CfCore.createInstance(
      mockSender as any,
      mockImpressionManager as any,
      false,
      ContentBlock.Core,
      device_id,
      false
    );

    const event: ItemProperties = {
      action: ItemAction.View,
      item: {
        id: "111",
        price: -50, // Negative price
        type: ItemType.Drug,
        quantity: 1,
        promo_id: "11111",
        currency: CurrencyCode.EUR,
        stock_status: StockStatus.InStock,
      },
    };

    // The function should throw an exception due to negative price
    await expect(
      ECommerce.logIngestEvent(ECommerceTypes.Item, event, true)
    ).rejects.toThrow();

    // add() should be called with an exception event when validation fails
    expect(addSpy).toHaveBeenCalledWith(
      expect.stringMatching(device_id),
      expect.stringMatching(device_id),
      expect.objectContaining({
        name: "exception",
        property: "item",
        ctx: expect.objectContaining({
          event_type: "item",
          exception_type: "Error",
          exception_source: "JS-SDK",
        }),
      }),
      false,
      false
    );

    // sendException() should not be called (exceptions are logged via add)
    expect(sendExceptionSpy).not.toHaveBeenCalled();
  });

  it("Should throw exception and call sendException when id is missing", async () => {
    const addSpy = jest.spyOn(mockSender, "add");
    const sendExceptionSpy = jest.spyOn(mockSender, "sendException");

    const cfCore = CfCore.createInstance(
      mockSender as any,
      mockImpressionManager as any,
      false,
      ContentBlock.Core,
      device_id,
      false
    );

    const event: ItemProperties = {
      action: ItemAction.View,
      item: {
        id: undefined as any, // Empty id
        price: 90,
        type: ItemType.Drug,
        quantity: 1,
        promo_id: "11111",
        currency: CurrencyCode.EUR,
        stock_status: StockStatus.InStock,
      },
    };

    // The function should throw an exception due to missing id
    await expect(
      ECommerce.logIngestEvent(ECommerceTypes.Item, event, true)
    ).rejects.toThrow();

    // add() should be called with an exception event when validation fails
    expect(addSpy).toHaveBeenCalledWith(
      expect.stringMatching(device_id),
      expect.stringMatching(device_id),
      expect.objectContaining({
        name: "exception",
        property: "item",
        ctx: expect.objectContaining({
          event_type: "item",
          exception_type: "Error",
          exception_source: "JS-SDK",
        }),
      }),
      false,
      false
    );

    // sendException() should not be called (exceptions are logged via add)
    expect(sendExceptionSpy).not.toHaveBeenCalled();
  });

  it("Should throw exception and call sendException when type is missing", async () => {
    const addSpy = jest.spyOn(mockSender, "add");
    const sendExceptionSpy = jest.spyOn(mockSender, "sendException");

    const cfCore = CfCore.createInstance(
      mockSender as any,
      mockImpressionManager as any,
      false,
      ContentBlock.Core,
      device_id,
      false
    );

    const event: ItemProperties = {
      action: ItemAction.View,
      item: {
        id: "111",
        price: 90,
        type: undefined as any, // Missing type
        quantity: 1,
        promo_id: "11111",
        currency: CurrencyCode.EUR,
        stock_status: StockStatus.InStock,
      },
    };

    // The function should throw an exception due to missing type
    await expect(
      ECommerce.logIngestEvent(ECommerceTypes.Item, event, true)
    ).rejects.toThrow();

    // add() should be called with an exception event when validation fails
    expect(addSpy).toHaveBeenCalledWith(
      expect.stringMatching(device_id),
      expect.stringMatching(device_id),
      expect.objectContaining({
        name: "exception",
        property: "item",
        ctx: expect.objectContaining({
          event_type: "item",
          exception_type: "Error",
          exception_source: "JS-SDK",
        }),
      }),
      false,
      false
    );

    // sendException() should not be called (exceptions are logged via add)
    expect(sendExceptionSpy).not.toHaveBeenCalled();
  });

  it("Should throw exception and call sendException when currency is missing", async () => {
    const addSpy = jest.spyOn(mockSender, "add");
    const sendExceptionSpy = jest.spyOn(mockSender, "sendException");

    const cfCore = CfCore.createInstance(
      mockSender as any,
      mockImpressionManager as any,
      false,
      ContentBlock.Core,
      device_id,
      false
    );

    const event: ItemProperties = {
      action: ItemAction.View,
      item: {
        id: "111",
        price: 90,
        type: ItemType.Drug,
        quantity: 1,
        promo_id: "11111",
        currency: undefined as any, // Missing currency
        stock_status: StockStatus.InStock,
      },
    };

    // The function should throw an exception due to missing currency
    await expect(
      ECommerce.logIngestEvent(ECommerceTypes.Item, event, true)
    ).rejects.toThrow();

    // add() should be called with an exception event when validation fails
    expect(addSpy).toHaveBeenCalledWith(
      expect.stringMatching(device_id),
      expect.stringMatching(device_id),
      expect.objectContaining({
        name: "exception",
        property: "item",
        ctx: expect.objectContaining({
          event_type: "item",
          exception_type: "Error",
          exception_source: "JS-SDK",
        }),
      }),
      false,
      false
    );

    // sendException() should not be called (exceptions are logged via add)
    expect(sendExceptionSpy).not.toHaveBeenCalled();
  });

  it("Should throw exception and call sendException when price is missing", async () => {
    const addSpy = jest.spyOn(mockSender, "add");
    const sendExceptionSpy = jest.spyOn(mockSender, "sendException");

    const cfCore = CfCore.createInstance(
      mockSender as any,
      mockImpressionManager as any,
      false,
      ContentBlock.Core,
      device_id,
      false
    );

    const event: ItemProperties = {
      action: ItemAction.View,
      item: {
        id: "111",
        price: undefined as any, // Missing price
        type: ItemType.Drug,
        quantity: 1,
        promo_id: "11111",
        currency: CurrencyCode.EUR,
        stock_status: StockStatus.InStock,
      },
    };

    // The function should throw an exception due to missing price
    await expect(
      ECommerce.logIngestEvent(ECommerceTypes.Item, event, true)
    ).rejects.toThrow();

    // add() should be called with an exception event when validation fails
    expect(addSpy).toHaveBeenCalledWith(
      expect.stringMatching(device_id),
      expect.stringMatching(device_id),
      expect.objectContaining({
        name: "exception",
        property: "item",
        ctx: expect.objectContaining({
          event_type: "item",
          exception_type: "Error",
          exception_source: "JS-SDK",
        }),
      }),
      false,
      false
    );

    // sendException() should not be called (exceptions are logged via add)
    expect(sendExceptionSpy).not.toHaveBeenCalled();
  });

  it("Should throw exception and call sendException when quantity is missing", async () => {
    const addSpy = jest.spyOn(mockSender, "add");
    const sendExceptionSpy = jest.spyOn(mockSender, "sendException");

    const cfCore = CfCore.createInstance(
      mockSender as any,
      mockImpressionManager as any,
      false,
      ContentBlock.Core,
      device_id,
      false
    );

    const event: ItemProperties = {
      action: ItemAction.View,
      item: {
        id: "111",
        price: 90,
        type: ItemType.Drug,
        quantity: undefined as any, // Missing quantity
        promo_id: "11111",
        currency: CurrencyCode.EUR,
        stock_status: StockStatus.InStock,
      },
    };

    // The function should throw an exception due to missing quantity
    await expect(
      ECommerce.logIngestEvent(ECommerceTypes.Item, event, true)
    ).rejects.toThrow();

    // add() should be called with an exception event when validation fails
    expect(addSpy).toHaveBeenCalledWith(
      expect.stringMatching(device_id),
      expect.stringMatching(device_id),
      expect.objectContaining({
        name: "exception",
        property: "item",
        ctx: expect.objectContaining({
          event_type: "item",
          exception_type: "Error",
          exception_source: "JS-SDK",
        }),
      }),
      false,
      false
    );

    // sendException() should not be called (exceptions are logged via add)
    expect(sendExceptionSpy).not.toHaveBeenCalled();
  });
});
