import React from "react";
import { useMutation, UseMutationOptions } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { Text } from "@mantine/core";
import { modals } from "@mantine/modals";
import getCommonQueryClient from "@/hooks/cruds/baseCrud/getCommonQueryClient";
import { HttpValidationError } from "@/lib/client";
import { sortByLexorank } from "@/utils/lexorank/sort";
import {
    BaseEntity,
    CreateMutationOptions,
    DeleteMutationOptions,
    UpdateMutationOptions,
} from "./types";

type CrudOperations<
    TEntity,
    TUpdate,
    TCreate,
    HasGetCreateEntity extends boolean,
> = {
    onCreate: HasGetCreateEntity extends true
        ? (data: Partial<TCreate>, onSuccess?: () => void) => void
        : (data: TCreate, onSuccess?: () => void) => void;
    onUpdate: (id: number, update: TUpdate, onSuccess?: () => void) => void;
    onDelete: (entity: TEntity, onSuccess?: () => void) => void;
};

type UseEntityOperationsProps<TEntity extends BaseEntity, TUpdate, TCreate> = {
    key: string;
    queryKey: any[];
    mutations: {
        create: UseMutationOptions<
            any,
            AxiosError<HttpValidationError>,
            CreateMutationOptions
        >;
        update: UseMutationOptions<
            any,
            AxiosError<HttpValidationError>,
            UpdateMutationOptions
        >;
        delete: UseMutationOptions<
            any,
            AxiosError<HttpValidationError>,
            DeleteMutationOptions
        >;
    };
    getCreateEntity?: (data: Partial<TCreate>) => TCreate | null;
    getUpdateEntity: (oldEntity: TEntity, update: TUpdate) => TEntity;
    getDeleteConfirmTitle: (entity: TEntity) => string;
};

function useCrudOperations<
    TEntity extends BaseEntity,
    TUpdate extends object,
    TCreate extends object,
>(
    props: UseEntityOperationsProps<TEntity, TUpdate, TCreate> & {
        getCreateEntity: (data: Partial<TCreate>) => TCreate | null;
    }
): CrudOperations<TEntity, TUpdate, TCreate, true>;

function useCrudOperations<
    TEntity extends BaseEntity,
    TUpdate extends object,
    TCreate extends object,
>(
    props: UseEntityOperationsProps<TEntity, TUpdate, TCreate> & {
        getCreateEntity?: undefined;
    }
): CrudOperations<TEntity, TUpdate, TCreate, false>;

function useCrudOperations<
    TEntity extends BaseEntity,
    TUpdate extends object,
    TCreate extends object,
>({
    key,
    queryKey,
    mutations,
    getCreateEntity,
    getUpdateEntity,
    getDeleteConfirmTitle,
}: UseEntityOperationsProps<TEntity, TUpdate, TCreate>): CrudOperations<
    TEntity,
    TUpdate,
    TCreate,
    boolean
> {
    const { queryClient, onError, onSettled } = getCommonQueryClient({
        queryKey,
        key,
    });

    const createMutation = useMutation({
        ...mutations.create,
        onError,
        onSettled,
    });

    const updateMutation = useMutation({
        ...mutations.update,
        onError,
        onSettled,
        onMutate: async ({ body: { entity: update } }) => {
            await queryClient.cancelQueries({ queryKey: [key] });

            const previous = queryClient.getQueryData(queryKey);

            queryClient.setQueryData(queryKey, (old: { items: TEntity[] }) => {
                if (!old) return;
                let updated = old.items.map((entity: TEntity) =>
                    entity.id === update.id
                        ? getUpdateEntity(entity, update)
                        : entity
                );

                if ("lexorank" in update) {
                    updated = sortByLexorank(
                        updated as (TEntity & { lexorank: string })[]
                    );
                }

                return {
                    ...old,
                    items: updated,
                };
            });

            return { previous };
        },
    });

    const deleteMutation = useMutation({
        ...mutations.delete,
        onError,
        onSettled,
        onMutate: async ({ path: { pk } }) => {
            await queryClient.cancelQueries({ queryKey: [key] });

            const previous = queryClient.getQueryData(queryKey);

            queryClient.setQueryData(queryKey, (old: { items: TEntity[] }) => {
                const filtered = old.items.filter(e => e.id !== pk);
                return {
                    ...old,
                    items: filtered,
                };
            });

            return { previous };
        },
    });

    const onCreate = (data: any, onSuccess?: () => void) => {
        let entity: TCreate;
        if (getCreateEntity) {
            const result = getCreateEntity(data);
            if (!result) return;
            entity = result;
        } else {
            entity = data;
        }

        createMutation.mutate(
            {
                body: {
                    entity,
                },
                path: undefined,
                query: undefined,
            },
            {
                onSuccess,
            }
        );
    };

    const onUpdate = (id: number, update: TUpdate, onSuccess?: () => void) => {
        updateMutation.mutate(
            {
                body: {
                    entity: update,
                },
                path: { pk: id },
                query: undefined,
            },
            {
                onSuccess,
            }
        );
    };

    const onDelete = (entity: TEntity, onSuccess?: () => void) => {
        modals.openConfirmModal({
            title: getDeleteConfirmTitle(entity),
            children: (
                <Text>Вы уверены, что хотите удалить "{entity.name}"?</Text>
            ),
            confirmProps: { color: "red" },
            onConfirm: () => {
                deleteMutation.mutate({ path: { pk: entity.id } } as any, {
                    onSuccess,
                });
            },
        });
    };

    return { onCreate, onUpdate, onDelete };
}

export default useCrudOperations;
