import { ICrudify } from "./interface/crudify.interface";
import { UseGuards, UsePipes, ValidationPipe } from "@nestjs/common";
import { DisableRouteGuard } from "./disable.guard";
import {
  ApiOperation,
  ApiCreatedResponse,
  ApiOkResponse,
  ApiNotFoundResponse,
  ApiBadRequestResponse,
  ApiConflictResponse,
  ApiParam,
  ApiBody,
  ApiQuery,
  ApiExcludeEndpoint,
} from "@nestjs/swagger";
import { ControllerMethods } from "./interface/controllermethods.type";
import { IRouteConfig } from "./interface/routeconfig.interface";

export namespace CrudifyRoutesDecorator {
  export function getDecorators(options: ICrudify, route: ControllerMethods) {
    const routeconfig: IRouteConfig | undefined =
      options.routes?.config?.[route];

    if (
      options.routes?.exclude?.includes(route) ||
      routeconfig?.disabled == true
    )
      return [ApiExcludeEndpoint(), UseGuards(DisableRouteGuard)];

    let routeDecorators: MethodDecorator[] = getRouteDecorators(options, route);

    const generalUserRouteDecorators: MethodDecorator[] =
      options?.routes?.decorators || [];
    const userRouteDecorators: MethodDecorator[] =
      routeconfig?.decorators || [];
    return [
      ...routeDecorators,
      ...generalUserRouteDecorators,
      ...userRouteDecorators,
    ];
  }

  function getRouteDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    switch (route) {
      case "create":
        return createDecorators(options, route);
      case "createBulk":
        return createBulkDecorators(options, route);
      case "findAll":
        return findAllDecorators(options, route);
      case "findOne":
        return findOneDecorators(options, route);
      case "put":
        return overwriteDecorators(options, route);
      case "update":
        return updateDecorators(options, route);
      case "updateBulk":
        return updateBulkDecorators(options, route);
      case "delete":
        return deleteDecorators(options, route);
      case "deleteBulk":
        return deleteBulkDecorators(options, route);
      case "restore":
        return restoreDecorators(options, route);
      case "restoreBulk":
        return restoreBulkDecorators(options, route);
      default:
        return [];
    }
  }

  function createDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    return [
      UsePipes(new ValidationPipe({ transform: true })),
      ApiOperation({
        summary: `Create a ${name} resource`,
        operationId: `${name}_${route}`,
      }),
      ApiCreatedResponse({
        description: `The resource  ${name} has been successfully created`,
        type: options.model.type,
      }),
      ApiBadRequestResponse({ description: "Invalid input data" }),
      ApiConflictResponse({
        description: "Conflict in creating the resource",
      }),
      ApiBody({
        description: "Data of the new resource",
        type: options.model.cdto || options.model.type,
      }),
    ];
  }

  function createBulkDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    return [
      UsePipes(new ValidationPipe({ transform: true })),
      ApiOperation({
        summary: `Create multiple ${name} resources`,
        operationId: `${name}_${route}`,
      }),
      ApiCreatedResponse({
        description: "The resources have been created",
        type: [options.model.type],
      }),
      ApiBadRequestResponse({ description: "Invalid input data" }),
      ApiConflictResponse({
        description: "Conflict in creating the resources",
      }),
      ApiBody({
        description: "Data of the new resources",
        type: [options.model.cdto || options.model.type],
      }),
    ];
  }

  function findAllDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    let path_types: any = {
      String: "string",
      Number: "number",
      Date: "string",
      Buffer: "string",
      Boolean: "boolean",
      Mixed: "string",
      ObjectId: "string",
      Array: "array",
      Decimal128: "number",
      Map: "object",
      Schema: "object",
      UUID: "string",
      BigInt: "number",
      Double: "number",
      Int32: "number",
    };
    let list: any[] = [];
    if (options.model.schema) {
      const fields = Object.keys(options.model.schema.paths);
      const operators = ["eq", "ne", "gt", "gte", "lt", "lte", "in"];
      fields.forEach((field) => {
        let type = options.model.schema!.paths[field].instance;
        let path_type = path_types[type];
        if (!path_type) {
          path_type = "string";
        }
        operators.forEach((operator) => {
          list.push({ [`${field}[${operator}]`]: { type: path_type } });
        });
      });
    }
    const patternProperties = list.reduce((acc, item) => {
      const key = Object.keys(item)[0];
      acc[key] = item[key];
      return acc;
    }, {});
    return [
      UsePipes(new ValidationPipe({ transform: true })),
      ApiOperation({
        summary: `Retrieve all ${name} resources`,
        operationId: `${name}_${route}`,
      }),
      ApiOkResponse({
        description: `List of ${name} resources`,
        type: [options.model.type],
      }),
      ApiNotFoundResponse({ description: "Resources not found" }),
      ApiQuery({
        name: "filters",
        required: false,
        type: "object",
        style: "form",
        explode: true,
        description: "Filters to apply",
        properties: patternProperties,
        examples: {
          void: { value: {} },
          example: {
            value: {
              "filter_string[eq]": "filter_value",
              "filter_num[gt]": -1,
            },
          },
        },
        default: {},
      }),
      ApiQuery({
        name: "limit",
        required: false,
        type: Number,
        description: "Number of records to return",
      }),
      ApiQuery({
        name: "skip",
        required: false,
        type: Number,
        description: "Number of records to skip",
      }),
      ApiQuery({
        name: "sort",
        required: false,
        type: String,
        description: "Sorting fields",
      }),
    ];
  }

  function findOneDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    return [
      UsePipes(new ValidationPipe({ transform: true })),
      ApiOperation({
        summary: `Retrive a ${name} resource by ID`,
        operationId: `${name}_${route}`,
      }),
      ApiOkResponse({
        description: `Resource ${name} found`,
        type: options.model.type,
      }),
      ApiNotFoundResponse({ description: "Resource not found" }),
      ApiParam({
        name: "id",
        description: "ID of the resource",
        type: String,
      }),
    ];
  }

  function overwriteDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    return [
      UsePipes(new ValidationPipe({ transform: true })),
      ApiOperation({
        summary: `Overwrite a ${name} resource`,
        operationId: `${name}_${route}`,
      }),
      ApiOkResponse({
        description: "The resource has been overwrited",
        type: options.model.type,
      }),
      ApiBadRequestResponse({ description: "Invalid data" }),
      ApiParam({
        name: "id",
        description: `ID of the ${name} resource`,
        type: String,
      }),
      ApiBody({
        description: "Overwrited data of the resource",
        type: options.model.udto || options.model.type,
      }),
    ];
  }

  function updateDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    return [
      UsePipes(new ValidationPipe({ transform: true })),
      ApiOperation({
        summary: `Update a ${name} resource`,
        operationId: `${name}_${route}`,
      }),
      ApiOkResponse({
        description: `The resource ${name} has been updated`,
        type: options.model.type,
      }),
      ApiBadRequestResponse({ description: "Invalid data" }),
      ApiParam({
        name: "id",
        description: `ID of the ${name} resource`,
        type: String,
      }),
      ApiBody({
        description: "Updated data of the resource",
        type: options.model.udto || options.model.type,
      }),
    ];
  }

  function updateBulkDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    return [
      UsePipes(new ValidationPipe({ transform: true })),
      ApiOperation({
        summary: `Update multiple ${name} resources`,
        operationId: `${name}_${route}`,
      }),
      ApiOkResponse({
        description: `The resources have been updated`,
        type: options.model.type,
      }),
      ApiBadRequestResponse({ description: "Invalid data" }),
      ApiBody({
        description: `Object containing filter and updated data`,
        schema: {
          type: "object",
          properties: {
            filter: {
              type: "object",
              description: "Filter to select the resources to update",
            },
            updateDto: {
              type: "object",
              description: "Data to apply to the selected resources",
            },
          },
        },
      }),
    ];
  }

  function deleteDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    return [
      ApiOperation({
        summary: `Delete a ${name} resource`,
        operationId: `${name}_${route}`,
      }),
      ApiOkResponse({ description: "The resource has been deleted" }),
      ApiNotFoundResponse({ description: "Resource not found" }),
      ApiParam({
        name: "id",
        description: "ID of the resource",
        type: String,
      }),
    ];
  }

  function deleteBulkDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    return [
      ApiOperation({
        summary: `Delete multiple ${name} resources`,
        operationId: `${name}_${route}`,
      }),
      ApiOkResponse({ description: "The resources have been deleted" }),
      ApiNotFoundResponse({ description: "Some resources not found" }),
      ApiBadRequestResponse({
        description: "Invalid input, expected an array of IDs",
      }),
      ApiBody({
        description: `Object containing filter`,
        schema: {
          type: "object",
          properties: {
            filter: {
              type: "object",
              description: "Filter to select the resources to delete",
            },
          },
        },
      }),
    ];
  }

  function restoreDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    return [
      UsePipes(new ValidationPipe({ transform: true })),
      ApiOperation({
        summary: `Restore a ${name} resource`,
        operationId: `${name}_${route}`,
      }),
      ApiOkResponse({
        description: `The resource ${name} has been restored`,
        type: options.model.type,
      }),
      ApiBadRequestResponse({ description: "Invalid data" }),
      ApiParam({
        name: "id",
        description: `ID of the ${name} resource`,
        type: String,
      }),
    ];
  }

  function restoreBulkDecorators(
    options: ICrudify,
    route: ControllerMethods
  ): MethodDecorator[] {
    const name: string = options.model.type.name.toLowerCase();
    return [
      UsePipes(new ValidationPipe({ transform: true })),
      ApiOperation({
        summary: `Restore multiple ${name} resources`,
        operationId: `${name}_${route}`,
      }),
      ApiOkResponse({
        description: `The resources have been restored`,
        type: options.model.type,
      }),
      ApiBadRequestResponse({ description: "Invalid data" }),
      ApiBody({
        description: `Object containing filter`,
        schema: {
          type: "object",
          properties: {
            filter: {
              type: "object",
              description: "Filter to select the resources to restore",
            },
          },
        },
      }),
    ];
  }
}
