import React from 'react'
import * as reduxHooks from '../../app/hooks'
import { useAIFeatures } from './hooks'
import { waitFor } from '@testing-library/react'
import { AIFeature, AIFeatureKind, AIFeatureType, AIResult, AIResultStatus } from '@zocks-communications/client-sdk-js'
import { AIFeaturesStatus } from './models'

const mockDispatch = jest.fn()
const mockSelector = jest.fn()

beforeEach(() => {
  // @ts-ignore
  jest.spyOn(React, 'useState').mockImplementation((initialState) => [initialState, jest.fn()])
  jest.spyOn(React, 'useEffect').mockImplementation((fn) => fn())
  jest.spyOn(React, 'useCallback').mockImplementation((fn) => fn)
  jest.spyOn(React, 'useMemo').mockImplementation((fn) => fn())
  jest.spyOn(reduxHooks, 'useZocksDispatch').mockReturnValue(mockDispatch)
  jest.spyOn(reduxHooks, 'useZocksStore').mockImplementation((selector) => mockSelector(selector))
})

afterEach(() => {
  jest.restoreAllMocks()
})

const aiFeatures: AIFeature[] = [
  {
    id: '1',
    name: 'Family',
    type: AIFeatureType.TOPIC_DETECTION,
    kind: AIFeatureKind.CUSTOM,
    generalConfig: {
      multiUserInput: false,
      userLevel: true,
      retranscribe: false,
      lifecycle: {
        realtime: true,
        fullContext: false,
      },
      saveContext: false,
    },
    config: {
      topicMinimumConfidence: 0.5,
      topics: ['children', 'divorce', 'separation'],
    },
    executionTargets: [],
    resultTargets: [],
    metadata: '',
  },
]

const aiResults: AIResult[] = [
  {
    aiFeatureId: '1',
    id: '1',
    roomId: 'testRoomId',
    sessionId: 'testSessionId',
    status: AIResultStatus.PROCESSED,
    createdAt: '2023-02-08T12:49:20.212613Z',
    userId: 'myUserId',
    result: {
      type: AIFeatureType.TOPIC_DETECTION,
      detectedTopic: 'children',
      confidence: 0.9,
      context: 'My son.',
    },
    scores: [],
  },
]

test('AI features are initialized when the useAIFeatures hook is invoked', async () => {
  const mockAIActions = {
    listAIFeatures: jest.fn().mockReturnValue(aiFeatures),
    listAIResults: jest.fn().mockReturnValue(aiResults),
  }
  mockSelector.mockReturnValueOnce({})
  mockSelector.mockReturnValueOnce(AIFeaturesStatus.INITIAL)

  useAIFeatures(mockAIActions)
  await waitFor(() => {
    expect(mockDispatch).toHaveBeenCalledTimes(4)
  })

  expect(mockDispatch).toHaveBeenNthCalledWith(1, {
    payload: 'loading',
    type: 'aiFeature/setAIFeaturesStatus',
  })

  expect(mockDispatch).toHaveBeenNthCalledWith(3, {
    payload: 'loading',
    type: 'aiFeature/setAIFeaturesStatus',
  })

  expect(mockDispatch).toHaveBeenCalledWith({
    payload: aiFeatures,
    type: 'aiFeature/initAIFeatures',
  })
  expect(mockDispatch).toHaveBeenCalledWith({
    payload: aiResults,
    type: 'aiFeature/initAIResults',
  })
  expect(mockAIActions.listAIFeatures).toHaveBeenCalledTimes(1)
  expect(mockAIActions.listAIResults).toHaveBeenCalledTimes(1)
})

test('when listing the AI features fails, the error is stored and AI results are not fetched', async () => {
  const mockAIActions = {
    listAIFeatures: jest.fn().mockRejectedValue({
      errors: [
        {
          message: 'Internal Server Error',
        },
      ],
    }),
    listAIResults: jest.fn(),
  }
  mockSelector.mockReturnValueOnce({})
  mockSelector.mockReturnValueOnce(AIFeaturesStatus.INITIAL)

  useAIFeatures(mockAIActions)
  await waitFor(() => {
    expect(mockDispatch).toHaveBeenCalledTimes(2)
  })

  expect(mockDispatch).toHaveBeenCalledWith({
    payload: 'loading',
    type: 'aiFeature/setAIFeaturesStatus',
  })

  expect(mockDispatch).toHaveBeenCalledWith({
    payload: {
      errors: [
        {
          message: 'Internal Server Error',
        },
      ],
    },
    type: 'aiFeature/setAIFeaturesError',
  })

  expect(mockAIActions.listAIFeatures).toHaveBeenCalledTimes(1)
  expect(mockAIActions.listAIResults).toHaveBeenCalledTimes(0)
})

test('when listing the AI features succeeds, but listing the AI results fails, the error is stored', async () => {
  const mockAIActions = {
    listAIFeatures: jest.fn().mockReturnValue(aiFeatures),
    listAIResults: jest.fn().mockRejectedValue({
      errors: [
        {
          message: 'Internal Server Error',
        },
      ],
    }),
  }
  mockSelector.mockReturnValueOnce({})
  mockSelector.mockReturnValueOnce(AIFeaturesStatus.INITIAL)

  useAIFeatures(mockAIActions)
  await waitFor(() => {
    expect(mockDispatch).toHaveBeenCalledTimes(4)
  })

  expect(mockDispatch).toHaveBeenNthCalledWith(1, {
    payload: 'loading',
    type: 'aiFeature/setAIFeaturesStatus',
  })

  expect(mockDispatch).toHaveBeenNthCalledWith(3, {
    payload: 'loading',
    type: 'aiFeature/setAIFeaturesStatus',
  })

  expect(mockDispatch).toHaveBeenCalledWith({
    payload: aiFeatures,
    type: 'aiFeature/initAIFeatures',
  })

  expect(mockDispatch).toHaveBeenCalledWith({
    payload: {
      errors: [
        {
          message: 'Internal Server Error',
        },
      ],
    },
    type: 'aiFeature/setAIFeaturesError',
  })

  expect(mockAIActions.listAIFeatures).toHaveBeenCalledTimes(1)
  expect(mockAIActions.listAIResults).toHaveBeenCalledTimes(1)
})

test('resetAIFeatures clears the state', async () => {
  const mockAIActions = {
    listAIFeatures: jest.fn(),
    listAIResults: jest.fn(),
  }
  mockSelector.mockReturnValueOnce({})
  mockSelector.mockReturnValueOnce(AIFeaturesStatus.INITIAL)

  const { resetAIFeatures } = useAIFeatures(mockAIActions)
  resetAIFeatures()

  expect(mockDispatch).toHaveBeenCalledWith({
    type: 'aiFeature/clearAIFeatures',
  })
})
