import mongoose, { QueryOptions } from "mongoose";
import { Request, Response } from "express";
import { check, matchedData } from "express-validator";
import {
  handleErrorResponse,
  handleHttpError,
} from "../utils/handlers/error.handle";
import { optionsPaginate } from "../config/paginationParams";

const mongoosePaginate = require("mongoose-paginate-v2");
const mongoosePaginateAggregate = require("mongoose-aggregate-paginate-v2");
const mongoseDelete = require("mongoose-delete");
import aqp from "api-query-params";
import { AbstractMethods } from "../methods/abstract.method";

export class AbstractController {
  methods: AbstractMethods;
  model;
  params;
  constructor(
    model?,
    params = {
      mongoose,
      mongoosePaginate,
      mongoosePaginateAggregate,
      mongoseDelete,
      check,
      matchedData,
      handleHttpError,
      handleErrorResponse,
      optionsPaginate,
      aqp: (query) => {
        const { filter, sort }: any = aqp(query);
        Object.keys(filter).forEach((f) => {
          if (
            mongoose.Types.ObjectId.isValid(filter[f]) &&
            filter[f].length === 24
          ) {
            filter[f] = new mongoose.Types.ObjectId(filter[f]);
          }
        });
        delete filter.page;
        return { filter, sort };
      },
    }
  ) {
    this.methods = new AbstractMethods(model);
    this.model = model;
    this.params = params;
  }

  /**
   * Get detail by single row
   * @param {*} req
   * @param {*} res
   */
  async getItem(req: Request, res: Response) {
    try {
      const matchedData = this.params.matchedData(req);
      const _id = matchedData._id;
      const data = await this.model.aggregate([
        {
          $match: { _id: new mongoose.Types.ObjectId(_id) },
        },
        ...this.methods.mainFilter(req),
      ]);
      res.send(data.length > 0 ? data[0] : {});
    } catch (e) {
      this.params.handleHttpError(res, e);
    }
  }
  /**
   *
   * @param {*} req
   * @param {*} res
   */
  async getItems(req: Request, res: Response, next) {
    try {
      const { filter, sort } = this.params.aqp(req.query);
      const [, options] = this.params.optionsPaginate(req);
      let aggregate = this.model.aggregate([
        {
          $match: filter,
        },
        {
          $match: { deleted: false },
        },
        ...this.methods.mainFilter(req),
      ]);
      const data = await this.model.aggregatePaginate(
        { ...aggregate },
        {
          ...options,
          sort,
        }
      );
      res.send(data);
    } catch (e) {
      this.params.handleHttpError(res, e);
    }
  }

  /**
   * Create record with public source
   * @param {*} req
   * @param {*} res
   */
  async createItem(req: Request, res: Response) {
    try {
      const matchedData = this.params.matchedData(req);
      const data = await this.methods.saveDocument(matchedData, req);
      res.send(data);
    } catch (e) {
      this.params.handleHttpError(res, e);
    }
  }

  /**
   * Update detail row
   * @param {*} req
   * @param {*} res
   */
  async updateItem(req: Request, res: Response) {
    try {
      const matchedData = this.params.matchedData(req);
      const { _id, ...body } = matchedData;
      const data = await this.model.findOneAndUpdate({ _id }, body, {
        new: true,
      });
      res.send(data);
    } catch (e) {
      this.params.handleHttpError(res, e);
    }
  }

  /**
   * Delete row
   * @param {*} req
   * @param {*} res
   */
  async deleteItem(req: Request, res: Response) {
    try {
      const matchedData = this.params.matchedData(req);
      const _id = matchedData._id;
      const findData = await this.model.delete({ _id });
      const data = {
        findData,
        deleted: true,
      };

      res.send(data);
    } catch (e) {
      this.params.handleHttpError(res, e);
    }
  }
}
