/**
 * @jest-environment jsdom
 */
import CfCore from "../../../core/CfCore"
import CatalogRepository, {CatalogRepositoryConfig} from "../../../core/repositories/catalog/CatalogRepository";
import ECommerce from "../../ECommerce";
import { DeliveryAction, ECommerceTypes } from "../../ECommerce/typings";
import Navigation from ".."

import {
    AppAction,
    MediaType,
    ContentBlock,
    IdentityAction,
    MediaAction,
    NavigationTypes,
    NudgeAction,
    NudgeResponseType,
    RateType,
    UserInfo, SearchModuleType,
} from "../typings";
import { ItemType } from "../../ECommerce/typings";

let windowSpy
let mockSender;

class MockImpressionsDetector {
    start() { }
    stop() { }
    restart() { }
}

describe('Navigation', () => {

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

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

    describe('Identification', () => {
        it('Should deliver events with deviceId instead of userId before identification', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            Navigation.logPageEvent({ path: '/mypath', title: 'page-title' })

            expect(senderSpy).toBeCalledWith(
                expect.stringMatching(DEFAULT_DEVICE_ID),
                expect.anything(),
                expect.anything(),
                expect.anything())
        })

        it('Should deliver events with userId once the user has been logged', () => {
            const DEFAULT_USER_ID = 'dummy_user'
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')

            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)
            bsCore.login(DEFAULT_USER_ID)

            Navigation.logPageEvent({ path: '/mypath', title: 'page-title' })

            expect(senderSpy).toBeCalledWith(
                expect.stringMatching(DEFAULT_USER_ID),
                expect.stringMatching(DEFAULT_DEVICE_ID),
                expect.anything(),
                expect.anything())
        })

        it('Should stop using user_id after logout', () => {
            const DEFAULT_USER_ID = 'dummy_user'
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')

            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)
            bsCore.login(DEFAULT_USER_ID)

            Navigation.logPageEvent({ path: '/mypath', title: 'page-title' })
            // bsCore.logout()

            // Navigation.logPageEvent({ path: '/mypath', title: 'page-title' })

            expect(senderSpy).lastCalledWith(
                expect.stringMatching(DEFAULT_USER_ID),
                expect.stringMatching(DEFAULT_DEVICE_ID),
                expect.anything(),
                expect.anything())
        })

    })

    describe('Navigation', () => {
        it('Should deliver an "App" event', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = {
                action: AppAction.Background,
                start_time : 0
            }

            Navigation.logIngestEvent(NavigationTypes.App, event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    name: NavigationTypes.App,
                    ctx: event
                }),
                expect.anything())
        })

        it('Should deliver a "Media" event', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = {
                type: MediaType.Image,
                id: 'id-media-event',
                action: MediaAction.View,
            }

            const sentEvent = {
                type: MediaType.Image,
                time: 0,
                id_source: 'id-media-event',
                id: 'image_id-media-event',
                action: MediaAction.Play,
            }

            Navigation.logIngestEvent(NavigationTypes.Media, event,)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    name: NavigationTypes.Media,
                    ctx: sentEvent
                }),
                expect.anything())
        })

        it('Should deliver a "Media" event with default type', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = {
                type: MediaType.Image,
                id: 'id-media-event',
                action: MediaAction.View,
            }

            Navigation.logIngestEvent(NavigationTypes.Media, event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    block: ContentBlock.Core
                }),
                expect.anything())
        })


        it('Should deliver a "Rate" event', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = {
                rate_value: 4,
                type: RateType.App,
                subject_id: 'app_id'
            }

            Navigation.logIngestEvent(NavigationTypes.Rate, event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    block: ContentBlock.Core
                }),
                expect.anything())
        })


        it('Should deliver a "Rate" event', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = {
                rate_value: 7,
                type: RateType.App,
                subject_id: 'app_id'
            }

            expect(() => Navigation.logIngestEvent(NavigationTypes.Rate, event)).toThrowError()
        })


        it('Should complain when mandatory fields are missing in media catalog', async () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const defaults: CatalogRepositoryConfig = {
                flushTime: 10 * 1000
            }

            const catalogRepository = new CatalogRepository(
                `dummy-url`,
                mockSender, defaults
            )

            Navigation.init(catalogRepository)
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = {
                type: MediaType.Video,
                time: 1000,
                id: 'id-media-event',
                action: MediaAction.Play,
            }

            await expect(Navigation.logIngestEvent(NavigationTypes.Media, event)).rejects.toThrowError()

        })

        it('Should deliver a "Media" event with e-elearning type', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = {
                contentBlock: ContentBlock.ELearning,
                type: MediaType.Image,
                id: 'id-media-event',
                action: MediaAction.View,
            }

            Navigation.logIngestEvent(NavigationTypes.Media, event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    block: ContentBlock.ELearning
                }),
                expect.anything())
        })

        it('Should deliver a "Page" event', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = { path: '/mypath', title: 'page-title' }

            Navigation.logIngestEvent(NavigationTypes.Page, event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    name: NavigationTypes.Page,
                    ctx: event
                }),
                expect.anything())
        })


        it('Should deliver a "Login" event', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const props: UserInfo = {
                action: IdentityAction.Login,
                user_props: {}
            }

            Navigation.logIdentifyEvent(props)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    name: NavigationTypes.Identify,
                    ctx: props
                }),
                expect.anything())
        })

        it('Should deliver a "Delivery" event with the content block set to ECommerce - no call to setCurrentBlock', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

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

            ECommerce.logDeliveryEvent(event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    type: ECommerceTypes.Delivery,
                    block: ContentBlock.ECommerce
                }),
                expect.anything())
        })


        it('Should deliver a "Delivery" event with the contentBlock set to ECommerce - call to setCurrentBlock', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = {
                id: "1",
                action: DeliveryAction.Delivered,
                order_id: '11',
            }
            Navigation.setCurrentBlock(ContentBlock.ELearning)

            ECommerce.logDeliveryEvent(event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    type: ECommerceTypes.Delivery,
                    block: ContentBlock.ECommerce
                }),
                expect.anything())
        })

        it('Should deliver a "Page" event with a default contentBlock that have been overwritten', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.ELearning, DEFAULT_DEVICE_ID, false)

            const event = { path: '/mypath', title: 'page-title' }

            Navigation.logIngestEvent(NavigationTypes.Page, event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    type: NavigationTypes.Page,
                    block: ContentBlock.ELearning
                }),
                expect.anything())
        })

        it('Should deliver a "Page" event with a default contentBlock that have been overwritten', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.ELearning, DEFAULT_DEVICE_ID, false)

            const event = { path: '/mypath', title: 'page-title' }

            Navigation.logIngestEvent(NavigationTypes.Page, event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    type: NavigationTypes.Page,
                    block: ContentBlock.ELearning
                }),
                expect.anything())
        })

        it('Should deliver a "Page" event after setting explicitily the block name', async () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = { path: '/mypath', title: 'page-title' }

            Navigation.setCurrentBlock(ContentBlock.ECommerce)

            await new Promise((r) => setTimeout(r, 20));

            Navigation.logIngestEvent(NavigationTypes.Page, event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    type: NavigationTypes.Page,
                    block: ContentBlock.ECommerce
                }),
                expect.anything())
        })

        it('Should discard page events where duration is 0', async () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = { path: '/mypath', title: 'page-title', duration: 0 }

            Navigation.logIngestEvent(NavigationTypes.Page, event)

            expect(senderSpy).toBeCalledTimes(0)
        })

        it('Should not discard page events if duration is not present', async () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = { path: '/mypath', title: 'page-title' }

            Navigation.logIngestEvent(NavigationTypes.Page, event)

            expect(senderSpy).toBeCalledTimes(1)
        })

        it('Should deliver a "PushNotification" event', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = {
                nudge_id: 111,
                type: NudgeResponseType.Push,
                response: {
                    action: NudgeAction.Discard
                },
                resolved_action: {}
            }

            Navigation.logPushNotificationEvent(event)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    name: NavigationTypes.ActionResponse,
                    ctx: event
                }),
                expect.anything())
        })


        it('Should deliver a "Search" event with a specific content block', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const userEvent = {
                module: SearchModuleType.Core,
                page: 0,
                query: 'query-string',
                results_list: [{id:'1', type: ItemType.Drug}, {id: '2', type: ItemType.Drug}],
                filter: { param: '11' }
            }

            const internalEvent = {
                page: 0,
                query: 'query-string',
                results_list: [{id:'1', type: ItemType.Drug}, {id: '2', type: ItemType.Drug}],
                filter: { param: '11' }
            }

            Navigation.logIngestEvent(NavigationTypes.Search, userEvent,true)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    name: NavigationTypes.Search,
                    ctx: expect.objectContaining(internalEvent)  // we don't care the id in this assertion
                }),
                expect.anything())

            // assert that the SDK has added the search ID
            expect((senderSpy.mock.calls[0][2] as any).props.id).toBeDefined()
            const eventDelivered = {
                block: ContentBlock.ECommerce
            }
            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(eventDelivered),
                expect.anything())
        })

        it('Should deliver a "Search" event for the default content block', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const event = {
                module: SearchModuleType.Core,
                page: 0,
                query: 'query-string',
                results_list: ['1', '2'],
                filter: { param: '11' }
            }

            const eventDelivered = {
                block: ContentBlock.Core
            }

            Navigation.logIngestEvent(NavigationTypes.Search, event, true)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(eventDelivered),
                expect.anything())

            // assert that the SDK has added the search ID
            expect((senderSpy.mock.calls[0][2] as any).props.id).toBeDefined()
        })


        it('Should deliver a "Search" event adapting list of ID to list of TypedItems', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const userEvent = {
                contentBlock: ContentBlock.ECommerce,
                page: 0,
                query: 'query-string',
                results_list: ['1', '2'],
                filter: { param: '11' }
            }

            const internalEvent = {
                module: SearchModuleType.Core,
                page: 0,
                query: 'query-string',
                results_list: [{id:'1', type: ItemType.Drug}, {id: '2', type: ItemType.Drug}],
                filter: { param: '11' }
            }

            Navigation.logIngestEvent(NavigationTypes.Search, userEvent, true)

            expect(senderSpy).toBeCalledWith(
                expect.anything(),
                expect.anything(),
                expect.objectContaining(
                {
                    type: NavigationTypes.Search,
                    props: expect.objectContaining(internalEvent)  // we don't care the id in this assertion
                }),
                expect.anything())
        })


        it('Should throw an exception on "Search" - malformed list of result_ids, first string, rest objects', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const userEvent = {
                module: SearchModuleType.Core,
                page: 0,
                query: 'query-string',
                results_list: [{id: '2', type: ItemType.Drug}],
                filter: { param: '11' }
            }


            expect(async () => Navigation.logIngestEvent(NavigationTypes.Search, userEvent, true)).rejects.toThrowError()
        })


        it('Should throw an exception on "Search" - malformed list of result_ids, first object, rest strings', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const userEvent = {
                module: SearchModuleType.ECommerce,
                page: 0,
                query: 'query-string',
                results_list: [ {id: '2', type: ItemType.Drug}],
                filter: { param: '11' }
            }

            expect(async () => Navigation.logIngestEvent(NavigationTypes.Search, userEvent, true)).rejects.toThrowError()
        })


        it('Should throw an exception on "Search" - repeated ids', () => {
            const DEFAULT_DEVICE_ID = 'web_sdk_device_id'

            const senderSpy = jest.spyOn(mockSender, 'add')
            const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false)

            const userEvent = {
                module: SearchModuleType.ECommerce,
                page: 0,
                query: 'query-string',
                results_list: [],
                filter: { param: '11' }
            }

            expect(async () => Navigation.logIngestEvent(NavigationTypes.Search, userEvent, true)).rejects.toThrowError()
        })
    })
})
