import _ from "lodash";
import sqlGenerator from "../../functions/dsql/sql/sql-generator";
import {
    DsqlCrudQueryObject,
    DsqlMethodCrudParam,
    ServerQueryParam,
} from "../../types";
import deserializeQuery from "../deserialize-query";
import EJSON from "../ejson";
import numberfy from "../numberfy";
import dsqlCrud from "./crud";

export type APIDataCrudQuery = ServerQueryParam & {
    page?: number;
};

export type CRUDResponseObject<P extends any = any> = {
    success: boolean;
    payload?: P;
    msg?: string;
    error?: string;
    queryObject?: ReturnType<Awaited<typeof sqlGenerator>>;
};

export default async function dsqlMethodCrud<
    T extends { [key: string]: any } = { [key: string]: any },
    P extends { [key: string]: any } = { [key: string]: any }
>({
    method,
    tableName,
    addUser,
    user,
    extraData,
    transformData,
    existingData,
    body,
    query,
    targetId,
    sanitize,
    transformQuery,
    debug,
}: DsqlMethodCrudParam<T>): Promise<CRUDResponseObject<P>> {
    let result: CRUDResponseObject = {
        success: false,
    };

    try {
        let finalBody = body;
        let finalQuery = deserializeQuery(query as any) as
            | DsqlCrudQueryObject<T>
            | undefined;

        let LIMIT = 10;
        let PAGE = 1;
        let OFFSET = (PAGE - 1) * LIMIT;

        if (method == "GET") {
            const newFinalQuery = _.cloneDeep(
                finalQuery || ({} as DsqlCrudQueryObject<T>)
            );
            Object.keys(newFinalQuery).forEach((key) => {
                const value = newFinalQuery[key];
                if (typeof value == "string" && value.match(/^\{|^\[/)) {
                    newFinalQuery[key] = EJSON.stringify(value);
                }
                if (value == "true") {
                    newFinalQuery[key] = true;
                }
                if (value == "false") {
                    newFinalQuery[key] = false;
                }
            });

            if (newFinalQuery.limit) LIMIT = numberfy(newFinalQuery.limit);
            if (newFinalQuery.page) PAGE = numberfy(newFinalQuery.page);
            OFFSET = (PAGE - 1) * LIMIT;

            finalQuery = newFinalQuery;
        }

        let finalData = finalBody
            ? ({
                  ...finalBody,
                  ...extraData,
              } as T)
            : ({} as T);

        if (user?.id && addUser) {
            finalData = {
                ...finalData,
                [addUser.field]: String(user.id),
            } as T;
        }

        if (transformData) {
            if (debug) {
                console.log("DEBUG:::transforming Data ...");
            }

            finalData = (await transformData({
                data: finalData,
                existingData: existingData,
                user,
                reqMethod: method,
            })) as T;
        }

        if (transformQuery) {
            if (debug) {
                console.log("DEBUG:::transforming Query ...");
            }

            finalQuery = await transformQuery({
                query: finalQuery || {},
                user,
                reqMethod: method,
            });
        }

        if (debug) {
            console.log("DEBUG:::finalQuery", finalQuery);
            console.log("DEBUG:::finalData", finalData);
        }

        switch (method) {
            case "GET":
                const GET_RESULT = await dsqlCrud({
                    action: "get",
                    table: tableName,
                    query: {
                        ...finalQuery,
                        query: {
                            ...finalQuery?.query,
                            ...(user?.id && addUser
                                ? {
                                      [addUser.field]: {
                                          value: String(user.id),
                                      },
                                  }
                                : undefined),
                        },
                        limit: LIMIT,
                        offset: OFFSET,
                    } as any,
                    sanitize,
                });

                result = {
                    success: Boolean(GET_RESULT?.success),
                    payload: GET_RESULT?.payload,
                    msg: GET_RESULT?.msg,
                    error: GET_RESULT?.error,
                    queryObject: GET_RESULT?.queryObject,
                };
                break;

            case "POST":
                const POST_RESULT = await dsqlCrud({
                    action: "insert",
                    table: tableName,
                    data:
                        finalData && Object.keys(finalData)?.[0]
                            ? finalData
                            : undefined,
                    sanitize,
                });
                result = {
                    success: Boolean(POST_RESULT?.success),
                    payload: POST_RESULT?.payload,
                    msg: POST_RESULT?.msg,
                    error: POST_RESULT?.error,
                };
                break;

            case "PUT":
                const PUT_RESULT = await dsqlCrud({
                    action: "update",
                    table: tableName,
                    data:
                        finalData && Object.keys(finalData)?.[0]
                            ? finalData
                            : undefined,
                    targetId,
                    sanitize,
                });
                result = {
                    success: Boolean(PUT_RESULT?.success),
                    payload: PUT_RESULT?.payload,
                    msg: PUT_RESULT?.msg,
                    error: PUT_RESULT?.error,
                };
                break;
            case "DELETE":
                const DELETE_RESULT = await dsqlCrud({
                    action: "delete",
                    table: tableName,
                    targetId,
                    sanitize,
                });
                result = {
                    success: Boolean(DELETE_RESULT?.success),
                    payload: DELETE_RESULT?.payload,
                    msg: DELETE_RESULT?.msg,
                    error: DELETE_RESULT?.error,
                };
                break;

            default:
                break;
        }
        return result;
    } catch (error) {
        return result;
    }
}
