import { FilterQuery, Query } from 'mongoose';

class QueryBuilder<T> {
  public modelQuery: Query<T[], T>;
  public query: Record<string, unknown>;

  constructor(modelQuery: Query<T[], T>, query: Record<string, unknown>) {
    this.modelQuery = modelQuery;
    this.query = query;
  }

  search(searchableFields: string[]) {
    const searchTerm = this?.query?.searchTerm;
    if (searchTerm) {
      this.modelQuery = this.modelQuery.find({
        $or: searchableFields.map(
          (field) =>
            ({
              [field]: { $regex: searchTerm, $options: 'i' },
            }) as FilterQuery<T>,
        ),
      });
    }
    return this;
  }

  filter() {
    const queryObj = { ...this.query }; // Shallow copy
    // Filtering
    const excludedFields = ['searchTerm', 'page', 'sort', 'limit', 'fields'];
    excludedFields.forEach((field) => delete queryObj[field]);

    // Check if filter has favorite field and convert it to boolean
    if (queryObj?.favorite) {
      queryObj.favorite = queryObj.favorite === 'true' ? true : queryObj.favorite === 'false' ? false : delete queryObj.favorite;
    }

    this.modelQuery = this.modelQuery.find(queryObj as FilterQuery<T>);
    return this;
  }

  sort(defaultSort: string = '-createdAt') {
    let sort = (this?.query?.sort as string)?.split(',')?.join(' ') || defaultSort;
    this.modelQuery = this.modelQuery.sort(sort as string);
    return this;
  }

  paginate(defaultPage: number = 1, defaultLimit: number = 10) {
    const page = Number(this?.query?.page) || defaultPage;
    const limit = Number(this?.query?.limit) || defaultLimit;
    const skip = (page - 1) * limit;

    this.modelQuery = this.modelQuery.skip(skip).limit(limit);
    return this;
  }

  fields(defaultFields: string = '-__v') {
    let  fields = (this?.query?.fields as string);

    // remove password from fields for security
    if(fields?.includes('password')) fields = fields?.replace('password', '');

    const queryFields = fields?.split(',')?.join(' ') || defaultFields;

    this.modelQuery = this.modelQuery.select(queryFields) as Query<T[], T>;
    return this;
  }


  async getMeta() {
    const totalQueries = this.modelQuery.getFilter();
    const total = await this.modelQuery.model.countDocuments(totalQueries);
    const page = Number(this?.query?.page) || 1;
    const limit = Number(this?.query?.limit) || 10;
    const totalPage = Math.ceil(total / limit);

    return {
      page,
      limit,
      total,
      totalPage,
    };
  }
}

export default QueryBuilder;
