import React, { PropsWithChildren } from 'react'
import { render } from '@testing-library/react'
import type { RenderOptions } from '@testing-library/react'
import { configureStore } from '@reduxjs/toolkit'
import { Provider } from 'react-redux'
import { MemoryRouter, Routes, Route } from 'react-router-dom'

import type { RootState } from '@/store'
import authReducer from '@/store/slices/authSlice'
import masterReducer from '@/store/slices/masterSlice'
import { MEMORY_ROUTER_TEST_FUTURE } from '@/testing/reactRouterTest'

// This type interface extends the default options for render from RTL, as well
// as allows the user to specify other things such as initialState, store.
interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
  preloadedState?: Partial<RootState>
  store?: ReturnType<typeof setupStore>
}

interface ProvidersAndRouteOptions extends ExtendedRenderOptions {
  initialEntry: string
  /** E.g. `/applications/:id` so `useParams` / `useNavigate` work under test */
  routePath: string
}

export function setupStore(preloadedState?: Partial<RootState>) {
  return configureStore({
    reducer: {
      auth: authReducer,
      master: masterReducer,
    } as any,
    preloadedState: preloadedState as any
  })
}

export function renderWithProviders(
  ui: React.ReactElement,
  {
    preloadedState = {},
    // Automatically create a store instance if no store was passed in
    store = setupStore(preloadedState),
    ...renderOptions
  }: ExtendedRenderOptions = {}
) {
  function Wrapper({ children }: PropsWithChildren<{}>): React.JSX.Element {
    return <Provider store={store}>{children}</Provider>
  }

  // Return an object with the store and all of RTL's query functions
  return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }
}

export function renderWithProvidersAndRoute(
  routedScreen: React.ReactElement,
  {
    initialEntry,
    routePath,
    ...rest
  }: ProvidersAndRouteOptions
) {
  const wrapped = (
    <MemoryRouter initialEntries={[initialEntry]} future={MEMORY_ROUTER_TEST_FUTURE}>
      <Routes>
        <Route path={routePath} element={routedScreen} />
      </Routes>
    </MemoryRouter>
  )
  return renderWithProviders(wrapped, rest)
}
