import type ITask from '@/models/ITask'
import {tasksTable} from '@/db/schema'
import {db} from '@/db/database'
import {eq} from 'drizzle-orm'
import {not} from 'drizzle-orm/sql/expressions/conditions'
import type {UseSuspenseQueryResult} from '@tanstack/react-query'
import {useMutation, type UseMutationResult, useQueryClient, useSuspenseQuery} from '@tanstack/react-query'
import type {IOnMutateResult} from '@/models/IOnMutateResult'

//region Mutations & queries

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

export const useGetTasks = (): UseSuspenseQueryResult<ITask[], Error> => {
  return useSuspenseQuery({
    queryKey: ['tasks'],
    queryFn: getTasks,
  })
}

export const useGetTask = (id?: number): UseSuspenseQueryResult<ITask | null, Error> => {
  return useSuspenseQuery({
    queryKey: ['tasks', id],
    queryFn: () => (id ? getTask(id) : null),
  })
}

export const useCreateTask = (): UseMutationResult<ITask, Error, CreateTasksParams, IOnMutateResult<ITask[]>> => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: createTask,
    onMutate: newTask => {
      const queryKey = ['tasks']
      const oldData = queryClient.getQueryData<ITask[]>(queryKey) ?? []
      queryClient.setQueryData<ITask[]>(queryKey, [...oldData, {...newTask, completed: false, id: -1, labels: []}])
      return {queryKey, oldData}
    },
    onSuccess: (data, _, context) => {
      if (context) {
        queryClient.setQueryData<ITask[]>(context.queryKey, [...context.oldData, data])
      }
    },
  })
}

export const useDeleteTask = (): UseMutationResult<void, Error, DeleteTasksParams, void> => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: deleteTask,
    onMutate: ({id}) => {
      queryClient.setQueryData<ITask[]>(['tasks'], old => (old ?? []).filter(task => task.id !== id))
      queryClient.setQueryData<ITask>(['tasks', id], undefined)
    },
  })
}

export const useUpdateTask = (): UseMutationResult<ITask, Error, ITask, IOnMutateResult<ITask>> => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: updateTask,
    onMutate: updatedTask => {
      const queryKey = ['tasks', updatedTask.id]
      const oldData = queryClient.getQueryData<ITask>(queryKey)!
      queryClient.setQueryData<ITask>(queryKey, updatedTask)
      queryClient.setQueryData<ITask[]>(['tasks'], old =>
        (old ?? []).map(t => (t.id === updatedTask.id ? updatedTask : t)),
      )
      return {queryKey, oldData}
    },
    onSuccess: (data, _, context) => {
      if (context) {
        queryClient.setQueryData<ITask>(context.queryKey, data)
      }
    },
  })
}

export const useToggleTaskStatus = (): UseMutationResult<
  ITask,
  Error,
  ToggleStatusParams,
  IOnMutateResult<ITask[]>
> => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: toggleTaskStatus,
    onMutate: ({id}) => {
      const queryKey = ['tasks']
      const oldData = queryClient.getQueryData<ITask[]>(queryKey) ?? []
      queryClient.setQueryData<ITask[]>(
        queryKey,
        oldData.map(t => {
          if (t.id === id) {
            return {...t, completed: !t.completed}
          }
          return t
        }),
      )
      return {queryKey, oldData}
    },
    onError: (_, __, context) => {
      if (context) {
        queryClient.setQueryData<ITask[]>(context.queryKey, context.oldData)
      }
    },
  })
}

//endregion

//region API functions

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

async function getTasks(): Promise<ITask[]> {
  const tasks = await db.select().from(tasksTable)
  return tasks.map(task => ({...task, labels: []}))
}

async function getTask(id: number): Promise<ITask> {
  const [task] = await db.select().from(tasksTable).where(eq(tasksTable.id, id))
  return {...task, labels: []}
}
interface CreateTasksParams {
  name: string
}

async function createTask({name}: CreateTasksParams): Promise<ITask> {
  // Via returning wordt de aangemaakte rij teruggegeven.
  const [task] = await db.insert(tasksTable).values({name}).returning()
  return {...task, labels: []}
}

interface DeleteTasksParams {
  id: number
}

async function deleteTask({id}: DeleteTasksParams): Promise<void> {
  await db.delete(tasksTable).where(eq(tasksTable.id, id))
}

async function updateTask({id, name, completed}: ITask): Promise<ITask> {
  // Via returning wordt de aangepast rij teruggegeven.
  const [task] = await db.update(tasksTable).set({name, completed}).where(eq(tasksTable.id, id)).returning()
  return {...task, labels: []}
}

interface ToggleStatusParams {
  id: number
}

async function toggleTaskStatus({id}: ToggleStatusParams): Promise<ITask> {
  const [task] = await db
    .update(tasksTable)
    .set({completed: not(tasksTable.completed)})
    .where(eq(tasksTable.id, id))
    .returning()
  return {...task, labels: []}
}

//endregion
