import { renderHook } from '@testing-library/react-hooks/native'
import React from 'react'
import useFlag from './useFlag'

const isEnabledMock = jest.fn()
const useContextSpy = jest.spyOn(React, 'useContext')
const givenFlagName: string = 'Test'
const clientMock: any = {
  on: jest.fn()
}

beforeEach(() => {
  isEnabledMock.mockClear()
})

test('should return false when the flag is NOT enabled in context', () => {
  isEnabledMock.mockReturnValue(false)
  useContextSpy.mockReturnValue({
    client: clientMock,
    isEnabled: isEnabledMock
  })
  const { result } = renderHook(() => useFlag(givenFlagName))

  expect(clientMock.on).toHaveBeenCalledWith('update', expect.any(Function))
  expect(clientMock.on).toHaveBeenCalledWith('ready', expect.any(Function))
  expect(result.current).toBe(false)
  expect(isEnabledMock).toHaveBeenCalledTimes(1)
})

test('should return true when the flag is enabled in context', () => {
  isEnabledMock.mockReturnValue(true)
  useContextSpy.mockReturnValue({
    client: clientMock,
    isEnabled: isEnabledMock
  })
  const { result } = renderHook(() => useFlag(givenFlagName))

  expect(clientMock.on).toHaveBeenCalledWith('update', expect.any(Function))
  expect(clientMock.on).toHaveBeenCalledWith('ready', expect.any(Function))
  expect(result.current).toBe(true)
  expect(isEnabledMock).toHaveBeenCalledTimes(1)
})

test('should return true when the client is ready and re-call isEnabled', () => {
  isEnabledMock.mockReturnValue(true)
  useContextSpy.mockReturnValue({
    client: clientMock,
    isEnabled: isEnabledMock
  })
  clientMock.on.mockImplementation((eventName: string, cb: Function) => {
    if (eventName === 'ready') {
      cb()
    }
  })

  const { result } = renderHook(() => useFlag(givenFlagName))

  expect(clientMock.on).toHaveBeenCalledWith('update', expect.any(Function))
  expect(clientMock.on).toHaveBeenCalledWith('ready', expect.any(Function))
  expect(result.current).toBe(true)
  expect(isEnabledMock).toHaveBeenCalledTimes(2)
})

test('should return true when the client is first false and is updated with true', () => {
  isEnabledMock.mockReturnValueOnce(false)
  isEnabledMock.mockReturnValueOnce(true)
  useContextSpy.mockReturnValue({
    client: clientMock,
    isEnabled: isEnabledMock
  })
  clientMock.on.mockImplementation((eventName: string, cb: Function) => {
    if (eventName === 'update') {
      cb()
    }
  })

  const { result } = renderHook(() => useFlag(givenFlagName))

  expect(result.current).toBe(true)
  expect(clientMock.on).toHaveBeenCalledWith('update', expect.any(Function))
  expect(clientMock.on).toHaveBeenCalledWith('ready', expect.any(Function))
  expect(isEnabledMock).toHaveBeenCalledTimes(3)
})

test('should set the local state only once', () => {
  isEnabledMock.mockReturnValueOnce(true)
  isEnabledMock.mockReturnValueOnce(true)
  useContextSpy.mockReturnValue({
    client: clientMock,
    isEnabled: isEnabledMock
  })
  clientMock.on.mockImplementation((eventName: string, cb: Function) => {
    if (eventName === 'update') {
      cb()
    }
  })

  const { result } = renderHook(() => useFlag(givenFlagName))

  expect(result.current).toBe(true)
  expect(isEnabledMock).toHaveBeenCalledTimes(2)
})

test('should NOT subscribe to ready or update if client does NOT exist', () => {
  clientMock.on.mockClear()
  isEnabledMock.mockReturnValueOnce(false)
  useContextSpy.mockReturnValue({ client: undefined, isEnabled: isEnabledMock })
  clientMock.on.mockImplementation((eventName: string, cb: Function) => {
    if (eventName === 'update') {
      cb()
    }
  })

  const { result } = renderHook(() => useFlag(givenFlagName))

  expect(result.current).toBe(false)
  expect(clientMock.on).not.toHaveBeenCalled()
  expect(clientMock.on).not.toHaveBeenCalled()
  expect(isEnabledMock).toHaveBeenCalledTimes(1)
})
