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

import { EventEmitter } from 'events'

import {
    CartAction,
    DeliveryAction,
    DrugProperties,
    ECommerceTypes,
    ItemAction,
    ListAction,
    ListType,
    ItemType,
    StockStatus,
    ScheduleDeliveryAction,
    CancelType
} from "../typings";

import {
    BloodProperties
} from "../blood.typings"

import { CurrencyCode } from "../../../core/commonTypes"
import { ICatalogRepository } from "../../../core/repositories/catalog/CatalogRepository";
import { MediaType, ContentBlock, MediaCatalog, UserCatalog } from "../../Navigation/typings";
import { toISOLocal } from "../../../utils";
import { ImpressionEventType } from "../../../core/impressions/typings";
import { MedicalEquipmentProperties } from "../medicalEquipment.typings";
import { OxygenProperties } from "../oxygen.typings";

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

let mockCatalogRepository: ICatalogRepository;

const device_id = 'asdf'
describe('ECommerce', () => {
    beforeAll(() => {
        mockSender = {
            add() { return false }
        }

        const mockCurrencyRepository = {
            injectBlood: (itemId: string, properties: BloodProperties) => new Promise(r => r()),
            injectDrug: (itemId: string, properties: DrugProperties) => new Promise(r => r()),
            injectMedia: (id: string, type: MediaType, properties: MediaCatalog) => { },
            injectMedicalEquipment: (itemId: string, properties: MedicalEquipmentProperties) => new Promise(r => r()),
            injectOxygen: (itemId: string, properties: OxygenProperties) => new Promise(r => r()),
            injectUser: (userId: string, properties: UserCatalog) => new Promise(r => r()),
        }

        ECommerce.init(mockCurrencyRepository, mockCatalogRepository)
    })

    beforeEach(() => {
        windowSpy = jest.spyOn(window, "window", "get");
    })

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

    it('Should deliver a "Checkout" 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 = {
            id: "1",
            cart_price: 10,
            currency: CurrencyCode.EUR,
            tax: 0,
            items: [{
                id: '111',
                price: 90,
                type: ItemType.Drug,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock
            }],
            cart_id: '11',
            is_successful: true
        }

        await ECommerce.logCheckoutEvent(event)

        expect(senderSpy).toBeCalledWith(
            expect.stringMatching(device_id),
            expect.stringMatching(device_id),
            expect.objectContaining(
            {
                type: ECommerceTypes.Checkout,
                props: { ...event}
            }),
            expect.anything())
    })


    it('Should thrown an exception on "Checkout" event with repeated items', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)

        const event = {
            id: "1",
            cart_price: 10,
            currency: CurrencyCode.EUR,
            tax: 0,
            items: [{
                id: '111',
                price: 90,
                type: ItemType.Drug,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock
            },{
                id: '111',
                price: 89,
                type: ItemType.Drug,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock
            },
        ],
            cart_id: '11',
            is_successful: true
        }

        expect(async () => await ECommerce.logCheckoutEvent(event)).rejects.toThrowError()

    })

    it('Should trigger an exception delivering a "Checkout" event with blood without meta', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)

        const event = {
            id: "1",
            cart_price: 10,
            currency: CurrencyCode.EUR,
            tax: 0,
            items: [{
                id: '111',
                price: 90,
                type: ItemType.Drug,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock
            }, {
                id: '222',
                price: 90,
                type: ItemType.Blood,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock
            },],
            cart_id: '11',
            is_successful: true
        }

        await expect(() => ECommerce.logCheckoutEvent(event)).rejects.toThrowError()
    })

    it('Should not trigger an exception delivering a "Checkout" event with blood with meta', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)

        const event = {
            id: "1",
            cart_price: 10,
            currency: CurrencyCode.EUR,
            tax: 0,
            items: [{
                id: '111',
                price: 90,
                type: ItemType.Drug,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock
            }, {
                id: '222',
                price: 90,
                type: ItemType.Blood,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock,
                meta: {
                    cross_matching: true,
                    temperature_strips: true,
                    extra_tests: true,
                    reason: 'asdf'
                }
            },],
            cart_id: '11',
            is_successful: true
        }

        await expect(() => ECommerce.logCheckoutEvent(event)).not.toThrowError()
    })

    it('Should trigger an Exception due currency missmatch in a "Checkout" event', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)

        const event = {
            id: "1",
            cart_price: 10,
            currency: CurrencyCode.EUR,
            tax: 0,
            items: [{
                id: '111',
                price: 90,
                type: ItemType.Drug,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock
            }, {
                id: '111',
                type: ItemType.Drug,
                price: 90,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.GBP,
                stock_status: StockStatus.InStock
            },
            ],
            cart_id: '11',
            is_successful: true
        }

        await expect(ECommerce.logCheckoutEvent(event)).rejects.toThrow('currency-missmatch')

    })

    it('Should deliver a "Delivery" event', () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)

        const event = {
            id: "1",
            action: DeliveryAction.Delivered,
            order_id: '11',
        }

        ECommerce.logDeliveryEvent(event)

        expect(senderSpy).toBeCalledWith(
            expect.stringMatching(device_id),
            expect.stringMatching(device_id),
            expect.objectContaining(
            {
                type: ECommerceTypes.Delivery,
                props: event
            }),
            expect.anything())
    })

    it('Should deliver a "Cancel" event', () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)

        const event = {
            id: "123",
            type: CancelType.Order,
            items: [{ id: '111', type: ItemType.Drug }],
            reason: "some text",
        }

        ECommerce.logCancelCheckoutEvent(event)

        expect(senderSpy).toBeCalledWith(
            expect.stringMatching(device_id),
            expect.stringMatching(device_id),
            expect.objectContaining(
            {
                type: ECommerceTypes.Cancel,
                props: event
            }),
            expect.anything())
    })

    it('Should thrown an exception on "Cancel" event due repeated ids', () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)

        const event = {
            id: "123",
            type: CancelType.Order,
            items: [{ id: '111', type: ItemType.Drug }, { id: '111', type: ItemType.Drug }],
            reason: "some text",
        }

        expect(async () => ECommerce.logCancelCheckoutEvent(event)).rejects.toThrowError()
    })

    it('Should throw an error delivering "Cancel" event with invalid types', () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)

        const event = {
            order_id: "123",
            types: [ItemType.Blood, ItemType.Electronics, "non-existing-type"],
            reason: "some text",
        }

        expect(() => ECommerce.logCancelOrderEvent(event)).toThrowError()

    })

    it('Should deliver a "Cart" event', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const bsCore = CfCore.createInstance(
            mockSender as any,
            mockImpressionManager as any,
            false,
            ContentBlock.Core,
            device_id, false)

        const event = {
            id: "1",
            action: CartAction.AddItem,
            cart_price: 10,
            currency: CurrencyCode.EUR,
            item: {
                id: '111',
                type: ItemType.Drug,
                price: 90,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock
            },
        }

        await ECommerce.logCartEvent(event)

        expect(senderSpy).toBeCalledWith(
            expect.stringMatching(device_id),
            expect.stringMatching(device_id),
            expect.objectContaining(
            {
                type: ECommerceTypes.Cart,
                props: { ...event}
            }),
            expect.anything())
    })

    it('Should trigger an Exception when currencies missmatch in "Cart" event', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const bsCore = CfCore.createInstance(
            mockSender as any,
            mockImpressionManager as any,
            false,
            ContentBlock.Core,
            device_id, false)

        const event = {
            id: "1",
            action: CartAction.AddItem,
            cart_price: 10,
            currency: CurrencyCode.BDT,
            item: {
                id: '111',
                price: 90,
                type: ItemType.Drug,
                quantity: 1,
                promo_id: '11111',
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock
            },
        }

        await expect(ECommerce.logCartEvent(event)).rejects.toThrow('currency-missmatch')

    })

    it('Should deliver an "Item" event', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)

        const event = {
            action: ItemAction.View,
            item: {
                id: '111',
                type: ItemType.Drug,
                quantity: 2,
                price: 100,
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock,
                promo_id: 'asdf'
            },
            search_id: "1"
        }

        await ECommerce.logItemEvent(event, {})

        expect(senderSpy).toBeCalledWith(
            expect.stringMatching(device_id),
            expect.stringMatching(device_id),
            expect.objectContaining(
            {
                type: ECommerceTypes.Item,
                props: { ...event}
            }),
            expect.anything())
    })

    it('Should not inject the drug when the action is not item_view', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug')

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

        const event = {
            action: ItemAction.Detail,
            item: {
                id: '111',
                quantity: 2,
                type: ItemType.Drug,
                price: 100,
                currency: CurrencyCode.EUR,
                stock_status: StockStatus.InStock,
                promo_id: 'asdf'
            },
            search_id: "1"
        }

        await ECommerce.logItemEvent(event, {})

        expect(catalogSpy).not.toBeCalled()
    })

    it('Should inject the drug when the action is item_view', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug')

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

        const event = {
            action: ItemAction.View,
            item: {
                id: '111',
                quantity: 2,
                type: ItemType.Drug,
                price: 100,
                currency: CurrencyCode.FKP,
                stock_status: StockStatus.InStock,
                promo_id: 'asdf'
            },
            search_id: "1"
        }

        await ECommerce.logItemEvent(event, { name: 'drugX', supplier: 'supplierY' })

        expect(catalogSpy).toBeCalled()
    })

    it('Should inject blood without meta', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectBlood')

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

        const event = {
            action: ItemAction.View,
            item: {
                id: '111',
                quantity: 2,
                type: ItemType.Blood,
                price: 100,
                currency: CurrencyCode.FKP,
                stock_status: StockStatus.InStock,
                promo_id: 'asdf'
            },
            search_id: "1"
        }

        const catalogProperties = {
            market_id: "",
            blood_component: 'aa',
            blood_group: 'bb',
            packaging_size: 50,
            packaging_units: 'units'
        }

        await expect(() => ECommerce.logItemEvent(event, catalogProperties)).not.toThrowError()
    })

    it('Should inject medicalEquipment data into catalog', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectMedicalEquipment')

        const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
        const event = {
            action: ItemAction.View,
            item: {
                id: '111',
                quantity: 2,
                type: ItemType.MedicalEquipment,
                price: 100,
                currency: CurrencyCode.FKP,
                stock_status: StockStatus.InStock,
                promo_id: 'asdf'
            },
            search_id: "1"
        }

        const catalogProperties = {
            name: 'medical equip. name',  // medical consumable name (e.g. 2-Way Catheter 16FR)
            market_id: '123',
            supplier_id: '123',
            supplier_name: '123-name',
            packaging_size: 50,
            packaging_units: 'units'
        }

        await ECommerce.logItemEvent(event, catalogProperties)
        expect(catalogSpy).toBeCalled()
    })


    it('Should inject the drug when the action is item_view - itemId must be inserted automatically', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug')

        const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
        const itemId = "111"
        const drugProperties = { name: 'drugX', supplier: 'supplierY' }
        const event = {
            action: ItemAction.View,
            item: {
                id: '111',
                quantity: 2,
                type: ItemType.Drug,
                price: 100,
                currency: CurrencyCode.FKP,
                stock_status: StockStatus.InStock,
                promo_id: 'asdf'
            },
            search_id: "1"
        }

        await ECommerce.logItemEvent(event, drugProperties)

        expect(catalogSpy).toBeCalledWith(itemId, drugProperties)
    })

    it('Should trigger an Exception when the item info is malformed', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)

        const event = {
            action: ItemAction.View,
            item: {
                id: '111',
                quantity: 2,
                price: 100,
                stock_status: StockStatus.InStock,
                promo_id: 'asdf'
            },
            search_id: "1"
        }

        await expect(ECommerce.logItemEvent(event as any, {})).rejects.toThrowError()
    })

    it('Should ingest data into item catalog when an item is impressed in the screen', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug')

        class ImpressionsDetectorMock extends EventEmitter {
            constructor() {
                super()
            }

            start() {
                setTimeout(() => {
                    console.log('timeout impression detector!')
                    const dataForCatalog = {
                        market_id: '2222',
                        name: 'drugname',
                        description: 'description',
                        supplier_id: 'supplier_id',
                        supplier_name: 'supplier_name',
                        packaging_size: 50,
                        packaging_units: 'units'
                    }

                    const data = {
                        dataset: {
                            logId: '1222',
                            logCurrency: 'USD',
                            logPrice: 123,
                            logQuantity: 2,
                            logStockStatus: 'in_stock',
                            logPromoId: '',
                            logDrugCatalogObject: JSON.stringify(dataForCatalog)
                        },
                        appData: {}
                    }

                    this.emit(ImpressionEventType.Impression, data)
                }, 20)
            }
        }

        const bsCore = CfCore.createInstance(mockSender as any, ImpressionsDetectorMock as any, false, ContentBlock.Core, device_id, false)

        ECommerce.startTrackingImpressions('container-classname', 'item-classname', '111')

        await new Promise((resolve) => setTimeout(() => resolve(''), 100))

        expect(catalogSpy).toBeCalled()
    })

    it('Should throw an exception when trying to restart impression with an unknown container classname', async () => {
        const senderSpy = jest.spyOn(mockSender, 'add')
        const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug')

        class ImpressionsDetectorMock extends EventEmitter {
            constructor() {
                super()
            }

            start() { }
        }

        const bsCore = CfCore.createInstance(mockSender as any, ImpressionsDetectorMock as any, false, ContentBlock.Core, device_id, false)

        ECommerce.startTrackingImpressions('container-classname', 'item-classname', '111')
        expect(() => ECommerce.restartTrackingImpressions('unknown-container', '111')).toThrowError()
    })

})
