import CfSender from './CfSender'

import { ContentBlock, NavigationTypes } from '../modules/Navigation/typings';
import { AppAction } from '../modules/Navigation/typings';
import {AppInfoObject, DeviceInfoObject, EventMainObject} from "./typings";

jest.useFakeTimers();
jest.spyOn(global, 'setInterval');

const numberOfRetries = 3

const senderOptions = {
    flushInterval: 10000,
    flushMaxRetries: numberOfRetries,
    baseUrl: 'www.base.url',
    ingestPath: '/ingestPath',
    allowAnonymousUsers: false,
    nudgeFetchPath: '/nudgePath',
    catalogPath: '/catalog',
    activateNudgeMechanism: true,
    selfManagedNudges: false,
    cacheEventsInLocalstorage: true,
    cacheEventsKey: 'cfLog',
    debug: true,
    pauseSDK: false
}

const deviceObject: DeviceInfoObject = {
    id: 'device_id',
    brand: "testBrand",
    model: "testBrand",
    os: 'linux',
    os_ver: 'linux',
}

const appObject: AppInfoObject = {
    id: "websiteURL",
    min_sdk_version: 21,
    target_sdk_version: 31,
    version: "1.0.3 (3)",
    version_code: 3,
    version_name: "1.0.3",
}

describe('CfSender', () => {

    it('Should send one event when flushing explicity', () => {
        const mockCfNetwork = {
            send: jest.fn( () => new Promise(r => r([]) )),
            get: jest.fn()
        }
        const notificationSpy = jest.spyOn(mockCfNetwork, 'send')

        const cfSender = new CfSender(mockCfNetwork, senderOptions)

        const event = {
            block: ContentBlock.Core,
            ol: true,
            ts: "2022-01-12T07:36:28Z",
            type: NavigationTypes.App,
            props: {
                action: AppAction.Open,
                start_time : 200
            }
        };

        cfSender.add("userId", "deviceId", event)

        cfSender.flush()

        // [0][1] -> first call, parameter 1
        expect(notificationSpy).toBeCalledWith(
            expect.anything(),
            {data:
                expect.arrayContaining([
                    expect.objectContaining({ ts: "2022-01-12T07:36:28Z" })
                ])
            }
        )
    })

    it('Should send one event when setting forceSend = true', () => {
        const mockCfNetwork = {
            send: jest.fn( () => new Promise(r => r([]) )),
            get: jest.fn()
        }
        const notificationSpy = jest.spyOn(mockCfNetwork, 'send')

        const cfSender = new CfSender(mockCfNetwork, senderOptions)

        const event = {
            block: ContentBlock.Core,
            ol: true,
            ts: "2022-01-12T07:36:28Z",
            type: NavigationTypes.App,
            props: {
                action: AppAction.Open,
                start_time : 200
            }
        };


        cfSender.add("userId", "deviceId", event, true)

        // [0][1] -> first call, parameter 1
        expect(notificationSpy).toBeCalledWith(
            expect.anything(),
            {data:
                expect.arrayContaining([
                    expect.objectContaining({ ts: "2022-01-12T07:36:28Z" })
                ])
            }
        )
    })

    it('Should NOT send the event immediately when not forced', () => {
        const mockCfNetwork = {
            send: jest.fn( () => new Promise(r => r([]) )),
            get: jest.fn()
        }
        const networkSendSpy = jest.spyOn(mockCfNetwork, 'send')

        const cfSender = new CfSender(mockCfNetwork, senderOptions)

        const event = {
            block: ContentBlock.Core,
            ol: true,
            ts: "2022-01-12T07:36:28Z",
            type: NavigationTypes.App,
            props: {
                action: AppAction.Open,
                start_time : 112
            }
        };

        cfSender.add("userId", "deviceId", event, false)

        // [0][1] -> first call, parameter 1
        expect(networkSendSpy).toBeCalledTimes(0)
    })

    it('Should not sent the event after reaching the maximum number of retries', async() => {
        const mockCfNetwork = {
            send: jest
                .fn()
                .mockRejectedValueOnce(new Error('Async error'))
                .mockRejectedValueOnce(new Error('Async error'))
                .mockRejectedValueOnce(new Error('Async error'))
                .mockResolvedValueOnce('first call'),
            get: jest.fn()
        }

        const notificationSpy = jest.spyOn(mockCfNetwork, 'send')

        const cfSender = new CfSender(mockCfNetwork, senderOptions)

        const event = {
            block: ContentBlock.Core,
            ol: true,
            ts: "2022-01-12T07:36:28Z",
            type: NavigationTypes.App,
            props: {
                action: AppAction.Open,
                start_time : 133
            }
        };

        cfSender.add("userId", "deviceId", event)

        await cfSender.flush()
        await cfSender.flush()
        await cfSender.flush()
        await cfSender.flush()
        await cfSender.flush()
        await cfSender.flush()
        await cfSender.flush()

        expect(notificationSpy).toBeCalledTimes(numberOfRetries + 1)


    })


    it('Should release the queue when flushing explicity', async () => {
        const mockCfNetwork = {
            send: jest.fn(() => Promise.resolve()),
            get: jest.fn()
        }
        const notificationSpy = jest.spyOn(mockCfNetwork, 'send')

        const cfSender = new CfSender(mockCfNetwork, senderOptions)

        const event = {
            block: ContentBlock.Core,
            ol: true,
            ts: "2022-01-12T07:36:28Z",
            type: NavigationTypes.App,
            props: {
                action: AppAction.Open,
                start_time : 131
            }
        };

        const eventMainObject: EventMainObject = {
            s_id: 'userId_0_0',
            u_id: 'u_id',
            app_info: appObject,
            d_info: deviceObject,
            sdk: `jsXCF/`,
            up: 90,
            dn: 100,
            data: [event]
        }

        cfSender.add("userId", "deviceId", event)

        await cfSender.flush()
        await cfSender.flush()

        // first "flush" will call it, but second one not, because the list
        // is already empty
        expect(notificationSpy).toHaveBeenCalledTimes(1)
    })

    it('Should enqueue event when there is a network error', async () => {
        const mockCfNetwork = {
            send: jest
                .fn()
                .mockRejectedValueOnce(new Error('Async error'))
                .mockResolvedValueOnce('first call'),
            get: jest.fn()
        }

        const notificationSpy = jest.spyOn(mockCfNetwork, 'send')

        const cfSender = new CfSender(mockCfNetwork, senderOptions)

        const event = {
            block: ContentBlock.Core,
            ol: true,
            ts: "2022-01-12T07:36:28Z",
            type: NavigationTypes.App,
            props: {
                action: AppAction.Open,
                start_time : 131
            }
        };

        const eventMainObject: EventMainObject = {
            s_id: 'userId_0_0',
            u_id: 'u_id',
            app_info: appObject,
            d_info: deviceObject,
            sdk: `jsXCF/`,
            up: 90,
            dn: 100,
            data: [event]
        }

        cfSender.add("userId", "deviceId", event)

        await cfSender.flush()
        await cfSender.flush()

        expect(notificationSpy).lastCalledWith(
            expect.anything(),
            {
                data: expect.arrayContaining([
                    expect.objectContaining({ ts: "2022-01-12T07:36:28Z" })
                ])
            }
        )

    })

    it('Should send all events receive from last flushing explicity - explicit flush', async () => {
        const mockCfNetwork = {
            send: jest.fn(() => Promise.resolve()),
            get: jest.fn()
        }

        const notificationSpy = jest.spyOn(mockCfNetwork, 'send')

        const cfSender = new CfSender(mockCfNetwork, senderOptions)


        const event = {
            block: ContentBlock.Core,
            ol: true,
            ts: "2022-01-12T07:36:28Z",
            type: NavigationTypes.App,
            props: {
                action: AppAction.Open,
                start_time : 122
            }
        };

        const eventMainObject: EventMainObject = {
            s_id: 'userId_0_0',
            u_id: 'u_id',
            app_info: appObject,
            d_info: deviceObject,
            sdk: `jsXCF/`,
            up: 90,
            dn: 100,
            data: [event]
        }


        cfSender.add("userId", "deviceId", event)
        cfSender.add("userId", "deviceId", event)
        cfSender.add("userId", "deviceId", event)

        await cfSender.flush()

        expect(notificationSpy).toHaveBeenCalledWith(
            expect.anything(),
            {
                data: expect.arrayContaining([
                    expect.objectContaining({ ts: "2022-01-12T07:36:28Z" }),
                    expect.objectContaining({ ts: "2022-01-12T07:36:29Z" }),
                    expect.objectContaining({ ts: "2022-01-12T07:36:30Z" }),
                ])
            }
        )
    })

})
