import { BuildProcedure, ProcedureBuilder } from "@trpc/server";
import { PaginatedRequest, PaginatedResponse, TData } from "../types";
import { getModelForClass } from "@typegoose/typegoose";
import { z } from "zod";
import { UseMutationResult, UseQueryResult } from "@tanstack/react-query";

export type QueryOperation = BuildProcedure<"query", any, any>;
export type MutateOperation = BuildProcedure<"mutation", any, any>;

export interface MongoCrudOverrides {
  create: MutateOperation;
  get: QueryOperation;
  update: MutateOperation;
  delete: MutateOperation;
}
export interface MongoCrudSchema {
  create: z.ZodSchema;
  get: z.ZodSchema;
  update: z.ZodSchema;
  delete: z.ZodSchema;
}

export interface MongoCrudReturn {
  create: MutateOperation;
  get: QueryOperation;
  update: MutateOperation;
  delete: MutateOperation;
}

export interface QueryableMongoCrudReturn<T extends TData> {
  get: {
    useQuery: (props: PaginatedRequest) => UseQueryResult<PaginatedResponse<T>>;
  };
  create: {
    useMutation: (props: Partial<T>) => UseMutationResult<T>;
  };
  update: {
    useMutation: (props: Partial<T>) => UseMutationResult<T>;
  };
  delete: {
    useMutation: (props: Partial<T>) => UseMutationResult;
  };
}

export interface MongoCrudProps {
  model: ReturnType<typeof getModelForClass>;
  procedure: ReturnType<ProcedureBuilder<any>["use"]>;
  inputSchema: MongoCrudSchema;
  overrides?: MongoCrudOverrides;
}

export const mongoCrud = <T extends TData>({
  model,
  procedure,
  inputSchema,
  overrides,
}: MongoCrudProps): MongoCrudReturn => {
  const createRecord: MutateOperation = procedure
    .meta({ description: `Create a ${model.modelName} record` })
    .input(inputSchema.create)
    .mutation(async ({ input }): Promise<T> => {
      return model.create(input);
    });

  const updateRecord: MutateOperation = procedure
    .meta({ description: `Update a ${model.modelName} record` })
    .input(inputSchema.update)
    .mutation(async ({ input }) => {
      const { _id, ...rest } = input;
      return model.findByIdAndUpdate(_id, rest, { new: true });
    });

  const getRecords: QueryOperation = procedure
    .meta({ description: `Get ${model.modelName} records` })
    .input(inputSchema.get)
    .query(async ({ input }): Promise<Array<T>> => {
      const { pageIndex, pageSize, filters, _id, ...rest } = input;
      if (_id) {
        return [(await model.findById(_id)) as T];
      }
      return model.find(rest);
    });

  const deleteRecord: MutateOperation = procedure
    .meta({ description: `Delete a ${model.modelName} record` })
    .input(inputSchema.delete)
    .mutation(async ({ input }) => {
      return model.findByIdAndDelete(input._id);
    });

  return {
    create: overrides?.create || createRecord,
    get: overrides?.get || getRecords,
    update: overrides?.update || updateRecord,
    delete: overrides?.delete || deleteRecord,
  };
};
