import type { SerializedError } from '@reduxjs/toolkit'
import { createSlice } from '@reduxjs/toolkit'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { renderHook, act } from '@testing-library/react'
import {
  actionsReducer,
  expectExactType,
  hookWaitFor,
  setupApiStore,
} from './helpers'

interface ResultType {
  result: 'complex'
}

interface ArgType {
  foo: 'bar'
  count: 3
}

const baseQuery = fetchBaseQuery({ baseUrl: 'https://example.com' })
const api = createApi({
  baseQuery,
  endpoints(build) {
    return {
      querySuccess: build.query<ResultType, ArgType>({
        query: () => '/success',
      }),
      querySuccess2: build.query({ query: () => '/success' }),
      queryFail: build.query({ query: () => '/error' }),
      mutationSuccess: build.mutation({
        query: () => ({ url: '/success', method: 'POST' }),
      }),
      mutationSuccess2: build.mutation({
        query: () => ({ url: '/success', method: 'POST' }),
      }),
      mutationFail: build.mutation({
        query: () => ({ url: '/error', method: 'POST' }),
      }),
    }
  },
})

const storeRef = setupApiStore(api, {
  ...actionsReducer,
})

const {
  mutationFail,
  mutationSuccess,
  mutationSuccess2,
  queryFail,
  querySuccess,
  querySuccess2,
} = api.endpoints

test('matches query pending & fulfilled actions for the given endpoint', async () => {
  const endpoint = querySuccess2
  const otherEndpoint = queryFail
  const { result } = renderHook(() => endpoint.useQuery({} as any), {
    wrapper: storeRef.wrapper,
  })
  await hookWaitFor(() => expect(result.current.isLoading).toBeFalsy())

  expect(storeRef.store.getState().actions).toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    api.internalActions.subscriptionsUpdated.match,
    endpoint.matchFulfilled
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    otherEndpoint.matchPending,
    api.internalActions.subscriptionsUpdated.match,
    otherEndpoint.matchFulfilled
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchFulfilled,
    api.internalActions.subscriptionsUpdated.match,
    endpoint.matchRejected
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    api.internalActions.subscriptionsUpdated.match,
    endpoint.matchRejected
  )
})
test('matches query pending & rejected actions for the given endpoint', async () => {
  const endpoint = queryFail
  const { result } = renderHook(() => endpoint.useQuery({}), {
    wrapper: storeRef.wrapper,
  })
  await hookWaitFor(() => expect(result.current.isLoading).toBeFalsy())
  expect(storeRef.store.getState().actions).toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    api.internalActions.subscriptionsUpdated.match,
    endpoint.matchRejected
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchFulfilled,
    endpoint.matchRejected
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    endpoint.matchFulfilled
  )
})

test('matches lazy query pending & fulfilled actions for given endpoint', async () => {
  const endpoint = querySuccess
  const { result } = renderHook(() => endpoint.useLazyQuery(), {
    wrapper: storeRef.wrapper,
  })
  act(() => void result.current[0]({} as any))
  await hookWaitFor(() => expect(result.current[1].isLoading).toBeFalsy())

  expect(storeRef.store.getState().actions).toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    api.internalActions.subscriptionsUpdated.match,
    endpoint.matchFulfilled
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchFulfilled,
    api.internalActions.subscriptionsUpdated.match,
    endpoint.matchRejected
  )

  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    api.internalActions.subscriptionsUpdated.match,
    endpoint.matchRejected
  )
})

test('matches lazy query pending & rejected actions for given endpoint', async () => {
  const endpoint = queryFail
  const { result } = renderHook(() => endpoint.useLazyQuery(), {
    wrapper: storeRef.wrapper,
  })
  act(() => void result.current[0]({}))
  await hookWaitFor(() => expect(result.current[1].isLoading).toBeFalsy())

  expect(storeRef.store.getState().actions).toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    api.internalActions.subscriptionsUpdated.match,
    endpoint.matchRejected
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchFulfilled,
    endpoint.matchRejected
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    endpoint.matchFulfilled
  )
})

test('matches mutation pending & fulfilled actions for the given endpoint', async () => {
  const endpoint = mutationSuccess
  const otherEndpoint = mutationSuccess2
  const { result } = renderHook(() => endpoint.useMutation(), {
    wrapper: storeRef.wrapper,
  })
  act(() => void result.current[0]({}))
  await hookWaitFor(() => expect(result.current[1].isLoading).toBeFalsy())

  expect(storeRef.store.getState().actions).toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    endpoint.matchFulfilled
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    otherEndpoint.matchPending,
    otherEndpoint.matchFulfilled
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchFulfilled,
    endpoint.matchRejected
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    endpoint.matchRejected
  )
})
test('matches mutation pending & rejected actions for the given endpoint', async () => {
  const endpoint = mutationFail
  const { result } = renderHook(() => endpoint.useMutation(), {
    wrapper: storeRef.wrapper,
  })
  act(() => void result.current[0]({}))
  await hookWaitFor(() => expect(result.current[1].isLoading).toBeFalsy())

  expect(storeRef.store.getState().actions).toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    endpoint.matchRejected
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchFulfilled,
    endpoint.matchRejected
  )
  expect(storeRef.store.getState().actions).not.toMatchSequence(
    api.internalActions.middlewareRegistered.match,
    endpoint.matchPending,
    endpoint.matchFulfilled
  )
})

test('inferred types', () => {
  createSlice({
    name: 'auth',
    initialState: {},
    reducers: {},
    extraReducers: (builder) => {
      builder
        .addMatcher(
          api.endpoints.querySuccess.matchPending,
          (state, action) => {
            expectExactType(undefined)(action.payload)
            // @ts-expect-error
            console.log(action.error)
            expectExactType({} as ArgType)(action.meta.arg.originalArgs)
          }
        )
        .addMatcher(
          api.endpoints.querySuccess.matchFulfilled,
          (state, action) => {
            expectExactType({} as ResultType)(action.payload)
            expectExactType(0 as number)(action.meta.fulfilledTimeStamp)
            // @ts-expect-error
            console.log(action.error)
            expectExactType({} as ArgType)(action.meta.arg.originalArgs)
          }
        )
        .addMatcher(
          api.endpoints.querySuccess.matchRejected,
          (state, action) => {
            expectExactType({} as SerializedError)(action.error)
            expectExactType({} as ArgType)(action.meta.arg.originalArgs)
          }
        )
    },
  })
})
