import { setItem, getItem } from './utils/local-storage'
import * as pageReloadUtils from './utils/page-reload'
import * as reportSentryUtils from './utils/report-sentry'
import cloudflareFetchHander from './cloudflare-fetch-handler'
import {
  DAVINCI_CLOUDFLARE_NUMBER_OF_RETRIES_KEY,
  DAVINCI_CLOUDFLARE_LAST_RETRY_DATE_KEY,
  MILLISECONDS_TO_NEXT_RETRY,
  MULTIPLE_RETRIES_ERROR_MESSAGE,
} from './constants'

jest.mock('./utils/local-storage')

describe('cloudflareFetchHander', () => {
  let pageReloadSpy: jest.SpyInstance<void, []>
  const getLocalStorageItemSpy = getItem as jest.Mock<
    ReturnType<typeof getItem>
  >
  const setLocalStorageItemSpy = setItem as jest.Mock<
    ReturnType<typeof setItem>
  >
  let reportToSentrySpy: jest.SpyInstance<void, [message: string]>

  beforeEach(() => {
    pageReloadSpy = jest
      .spyOn(pageReloadUtils, 'pageReload')
      .mockImplementation(jest.fn())
    getLocalStorageItemSpy.mockReturnValue(null)
    setLocalStorageItemSpy.mockReturnValue(undefined)
    reportToSentrySpy = jest
      .spyOn(reportSentryUtils, 'reportToSentry')
      .mockImplementation(jest.fn())
  })

  afterEach(() => {
    jest.restoreAllMocks()
    getLocalStorageItemSpy.mockReset()
    setLocalStorageItemSpy.mockReset()
  })

  it('does nothing if status is 200 OK', () => {
    const okResponse = {
      status: 200,
    }

    // Act
    cloudflareFetchHander(okResponse as Response)

    expect(pageReloadSpy).not.toHaveBeenCalled()
    expect(getLocalStorageItemSpy).not.toHaveBeenCalled()
    expect(setLocalStorageItemSpy).not.toHaveBeenCalled()
  })

  it('reloads the page if status 429 Too Many Requests and it is a first request', () => {
    const tooManyRequestsResponse = {
      status: 429,
    }
    const numberOfRetries = null
    const expectedUpdatedNumberOfRetries = '1'

    getLocalStorageItemSpy.mockReturnValueOnce(numberOfRetries)

    // Act
    cloudflareFetchHander(tooManyRequestsResponse as Response)

    expect(setLocalStorageItemSpy).toHaveBeenNthCalledWith(
      1,
      DAVINCI_CLOUDFLARE_LAST_RETRY_DATE_KEY,
      expect.any(String)
    )
    expect(setLocalStorageItemSpy).toHaveBeenNthCalledWith(
      2,
      DAVINCI_CLOUDFLARE_NUMBER_OF_RETRIES_KEY,
      expectedUpdatedNumberOfRetries
    )
    expect(pageReloadSpy).toHaveBeenCalled()
  })

  it('reloads the page if status 429 and number of retries is not max', () => {
    const tooManyRequestsResponse = {
      status: 429,
    }
    const numberOfRetries = '2'
    const expectedUpdatedNumberOfRetries = '3'

    getLocalStorageItemSpy.mockReturnValueOnce(numberOfRetries)

    // Act
    cloudflareFetchHander(tooManyRequestsResponse as Response)

    expect(setLocalStorageItemSpy).toHaveBeenNthCalledWith(
      1,
      DAVINCI_CLOUDFLARE_LAST_RETRY_DATE_KEY,
      expect.any(String)
    )
    expect(setLocalStorageItemSpy).toHaveBeenNthCalledWith(
      2,
      DAVINCI_CLOUDFLARE_NUMBER_OF_RETRIES_KEY,
      expectedUpdatedNumberOfRetries
    )
    expect(pageReloadSpy).toHaveBeenCalled()
  })

  it('stops reloading the page after 3rd retry if last retry was not long ago', () => {
    const tooManyRequestsResponse = {
      status: 429,
    }
    const numberOfRetries = '3'
    const notLongAgoLastRetryDate = Date.now()

    getLocalStorageItemSpy.mockReturnValueOnce(numberOfRetries)
    getLocalStorageItemSpy.mockReturnValueOnce(String(notLongAgoLastRetryDate))

    // Act
    cloudflareFetchHander(tooManyRequestsResponse as Response)

    expect(pageReloadSpy).not.toHaveBeenCalled()
    expect(setLocalStorageItemSpy).not.toHaveBeenCalled()
  })

  it('reloads the page after 3rd retry if last retry was long ago', () => {
    const tooManyRequestsResponse = {
      status: 429,
    }
    const numberOfRetries = '3'
    const expectedUpdatedNumberOfRetries = '0'
    const longAgoLastRetryDate =
      new Date().getTime() - 2 * MILLISECONDS_TO_NEXT_RETRY

    getLocalStorageItemSpy.mockReturnValueOnce(numberOfRetries)
    getLocalStorageItemSpy.mockReturnValueOnce(String(longAgoLastRetryDate))

    // Act
    cloudflareFetchHander(tooManyRequestsResponse as Response)

    expect(setLocalStorageItemSpy).toHaveBeenNthCalledWith(
      1,
      DAVINCI_CLOUDFLARE_LAST_RETRY_DATE_KEY,
      expect.any(String)
    )
    expect(setLocalStorageItemSpy).toHaveBeenNthCalledWith(
      2,
      DAVINCI_CLOUDFLARE_NUMBER_OF_RETRIES_KEY,
      expectedUpdatedNumberOfRetries
    )
    expect(pageReloadSpy).toHaveBeenCalled()
    expect(reportToSentrySpy).not.toHaveBeenCalled()
  })

  it('reports to Sentry if multiple retries happen n short amount of time', () => {
    const tooManyRequestsResponse = {
      status: 429,
    }
    const numberOfRetries = '1'
    const shortAmountOfTimeAfterLastRetryDate = new Date().getTime() - 1

    getLocalStorageItemSpy.mockReturnValueOnce(numberOfRetries)
    getLocalStorageItemSpy.mockReturnValueOnce(
      String(shortAmountOfTimeAfterLastRetryDate)
    )

    // Act
    cloudflareFetchHander(tooManyRequestsResponse as Response)

    expect(reportToSentrySpy).toHaveBeenCalledWith(
      MULTIPLE_RETRIES_ERROR_MESSAGE
    )
  })
})
