/**
 * @jest-environment jsdom
 */

import { useConsoleLogger } from '../devTools'
import Transport from '../logger/log-service-transport'
import { createLogger } from '../logger'
import TimeOnPageTracker from '../time-on-page-tracker'
import { makeContext, makeSettings } from '../../__tests__/helpers'
import EnhancedContentApi, { EcRenderConfig } from '../../enhancedContent'

jest.mock('../devTools')
jest.mock('../logger/log-service-transport')

const log = jest.fn()
const ecApi = new EnhancedContentApi(makeSettings(), makeContext(), { log })
const lastEcRenderConfigSpy = jest.spyOn(ecApi, 'lastRenderConfig', 'get')

describe('time on page tracker', () => {
  describe('log time on page event', () => {
    beforeEach(() => {
      ;(Transport as jest.Mock).mockClear()
      jest.useFakeTimers()
      ;(useConsoleLogger as jest.Mock).mockReturnValue(false)
    })

    test('should fire event on interval', () => {
      const logger = createLogger(makeContext(), makeSettings())
      const tracker = new TimeOnPageTracker(logger)
      tracker.start()

      jest.advanceTimersByTime(5000)
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(1)

      jest.advanceTimersByTime(5000 * Math.pow(2, 2))
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(2)

      jest.advanceTimersByTime(5000 * Math.pow(3, 2))
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(3)
    })

    test('should fire event on visibilitychange to hidden', () => {
      const logger = createLogger(makeContext(), makeSettings())
      const tracker = new TimeOnPageTracker(logger)
      tracker.start()

      jest.spyOn(document, 'visibilityState', 'get').mockReturnValue('hidden')

      document.dispatchEvent(new Event('visibilitychange', { bubbles: true, cancelable: true }))
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(1)
    })

    test('should fire event on visibilitychange to visible', () => {
      const logger = createLogger(makeContext(), makeSettings())
      const tracker = new TimeOnPageTracker(logger)
      tracker.start()

      jest.spyOn(document, 'visibilityState', 'get').mockReturnValue('visible')

      document.dispatchEvent(new Event('visibilitychange', { bubbles: true, cancelable: true }))
      // No event fire when switch to visible, but it will start the timer
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(0)

      jest.advanceTimersByTime(5000)
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(1)
    })

    test('stop will stop events', () => {
      const logger = createLogger(makeContext(), makeSettings())
      const tracker = new TimeOnPageTracker(logger)
      tracker.start()
      tracker.stop()

      jest.advanceTimersByTime(5000)
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(0)

      jest.spyOn(document, 'visibilityState', 'get').mockReturnValue('hidden')

      document.dispatchEvent(new Event('visibilitychange', { bubbles: true, cancelable: true }))
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(0)
    })

    test('restart will restart events', () => {
      const logger = createLogger(makeContext(), makeSettings())
      const tracker = new TimeOnPageTracker(logger)
      tracker.start()

      jest.advanceTimersByTime(5000)
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(1)

      tracker.restart()

      // After restart, the first event will be sent after 5 seconds
      jest.advanceTimersByTime(5000)
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(2)
    })

    test('should include last EC render after render', () => {
      const logger = createLogger(makeContext(), makeSettings())
      const tracker = new TimeOnPageTracker(logger, ecApi)
      tracker.start()

      jest.advanceTimersByTime(5000)
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(1)
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenLastCalledWith(
        'time_on_page',
        expect.objectContaining({
          lastEcRenderConfig: undefined,
        }),
        expect.any(Object)
      )

      const lastEcRenderConfig: EcRenderConfig = {
        productId: '1234',
        idType: 'SKU',
        content: [
          { source: 'foo.html', weight: 0.95 },
          { source: null, weight: 0.05 },
        ],
        allContentExists: true,
        source: 'foo.html',
        sourceExists: true,
      }
      lastEcRenderConfigSpy.mockReturnValue(lastEcRenderConfig)
      jest.advanceTimersByTime(5000 * Math.pow(2, 2))
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenCalledTimes(2)
      expect((Transport as jest.Mock).mock.instances[0].log).toHaveBeenLastCalledWith(
        'time_on_page',
        expect.objectContaining({ lastEcRenderConfig }),
        expect.any(Object)
      )
    })
  })
})
