import {faker} from '@faker-js/faker'

import {
  STORAGE_KEY,
  persistToDatabase,
  retrieveFromDatabase,
} from '@/api/NIET_OPENEN_WORDT_GEBRUIKT_DOOR_DE_API_FILES/databaseSimulation.ts'
import type {QueryKey, UseMutationResult, UseSuspenseQueryResult} from '@tanstack/react-query'
import {useMutation, useQueryClient, useSuspenseQuery} from '@tanstack/react-query'
import type {IArtifact} from '@/models/IArtifact.ts'
import {artifacts} from '@/api/NIET_OPENEN_WORDT_GEBRUIKT_DOOR_DE_API_FILES/data/artifacts.ts'

export interface OnMutateResult<T> {
  oldData: T
  queryKey: QueryKey
}

//region Mutations & queries

/**
 * ---------------------------------------------------------------------------------------------------------------------
 *                                          MUTATIONS & QUERIES
 * ---------------------------------------------------------------------------------------------------------------------
 */

export const useGetArtifacts = (): UseSuspenseQueryResult<IArtifact[], Error> => {
  return useSuspenseQuery({
    queryKey: ['artifacts'],
    queryFn: getArtifacts,
  })
}

export const useGetArtifact = (id: number): UseSuspenseQueryResult<IArtifact, Error> => {
  return useSuspenseQuery({
    queryKey: ['artifacts', id],
    queryFn: () => getArtifact(id),
  })
}

export const useAddArtifact = (): UseMutationResult<IArtifact, Error, void> => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: addArtefact,
    onSuccess: newArtifact => {
      queryClient.setQueryData<IArtifact[]>(['artifacts'], old => [...(old ?? []), newArtifact])
    },
  })
}

export const useDeleteArtifact = (): UseMutationResult<void, Error, DeleteArtifactParams> => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: deleteArtifact,
    onSuccess: (_, {id}) => {
      queryClient.setQueryData<IArtifact[]>(['artifacts'], old => (old ?? []).filter(a => a.id !== id))
    },
  })
}

export const useUpdateArtifact = (): UseMutationResult<
  IArtifact,
  Error,
  UpdateArtifactParams,
  OnMutateResult<IArtifact>
> => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: updateArtifact,
    onMutate: async ({id, artifact}) => {
      const queryKey = ['artifacts', id]
      await queryClient.cancelQueries({queryKey})
      const oldData = queryClient.getQueryData<IArtifact>(queryKey)!
      queryClient.setQueryData<IArtifact>(queryKey, {
        ...oldData,
        ...artifact,
      })
      return {oldData, queryKey}
    },
    onError: (_, __, onMutateResult) => {
      if (onMutateResult) {
        queryClient.setQueryData<IArtifact>(onMutateResult.queryKey, onMutateResult.oldData)
      }
    },
  })
}

//endregion

//region API functions

/**
 * ---------------------------------------------------------------------------------------------------------------------
 *                                          API functions
 * ---------------------------------------------------------------------------------------------------------------------
 */

/**
 * Haal alle artefacten op uit de database.
 */
const getArtifacts = (): Promise<IArtifact[]> => {
  return retrieveFromDatabase<IArtifact[]>(STORAGE_KEY)
}

/**
 * Haal een specifiek artefact op uit de database.
 *
 * @param id Het id van het artefact dat opgehaald moet worden.
 */
const getArtifact = async (id: number): Promise<IArtifact> => {
  const artifacts = await getArtifacts()
  return artifacts.find(s => s.id === id)!
}

interface DeleteArtifactParams {
  id: number
}

/**
 * Verwijder een specifiek artefact uit de database.
 *
 * @param id Het id van het artefact dat verwijderd moet worden.
 */
const deleteArtifact = async ({id}: DeleteArtifactParams): Promise<void> => {
  // Wait 1.5 seconds to display the delete animation.
  await new Promise(r => setTimeout(r, 1000))
  let artifacts = await getArtifacts()
  artifacts = artifacts.filter(a => a.id !== id)
  await persistToDatabase(
    STORAGE_KEY,
    artifacts.filter(b => b.id !== id),
  )
}

interface UpdateArtifactParams {
  id: number
  artifact: Partial<IArtifact>
}

const updateArtifact = async ({id, artifact}: UpdateArtifactParams): Promise<IArtifact> => {
  const artifacts = await getArtifacts()
  const updateIndex = artifacts.findIndex(a => a.id === id)
  artifacts[updateIndex] = {
    ...artifacts[updateIndex],
    ...artifact,
  }
  await persistToDatabase(STORAGE_KEY, artifacts)
  return artifacts[updateIndex]
}

/**
 * Voeg een nieuw willekeurig pokemon toe aan de database.
 */
const addArtefact = async (): Promise<IArtifact> => {
  const allArtifacts = artifacts
  const artifactsInDB = await getArtifacts()
  const dbIds = new Set(artifactsInDB.map(b => b.id))

  const filteredArtifacts = allArtifacts.filter(b => !dbIds.has(b.id))
  if (filteredArtifacts.length === 0) {
    throw new Error('All 35 artifacts have already been added to the museum collection.')
  }

  const selectedArtifact = faker.helpers.arrayElement(filteredArtifacts)

  await persistToDatabase(STORAGE_KEY, [...artifactsInDB, selectedArtifact])
  return selectedArtifact
}
