import { ModelDefined } from "sequelize";

import {
  IRepositoryQueryBuilder,
  IRepositoryQueryOptions,
} from "@/contexts/common/domain/repositories";

import {
  FilterCriteria,
  IQueryCriteria,
  OffsetPaging,
  OrderCriteria,
  QuickSearchCriteria,
} from "@shared/contexts";
import { SequelizeParseFilter } from "./SequelizeParseFilter";
import { SequelizeParseOrder } from "./SequelizeParseOrder";

export interface ISequelizeQueryOptions extends IRepositoryQueryOptions {
  model: ModelDefined<any, any>;
}

export interface ISequelizeQueryBuilder extends IRepositoryQueryBuilder {
  generateQuery: (props: { model: any; queryCriteria?: IQueryCriteria }) => ISequelizeQueryOptions;
}

export class SequelizeQueryBuilder implements ISequelizeQueryBuilder {
  public static create() {
    return new SequelizeQueryBuilder();
  }

  private applyPagination(pagination: OffsetPaging): any {
    const limit = pagination.limit;
    const offset = pagination.offset * limit;

    return {
      offset,
      limit,
      subQuery: false, // <- https://selleo.com/til/posts/ddesmudzmi-offset-pagination-with-subquery-in-sequelize-
    };
  }

  private applyQuickSearch(
    model: ModelDefined<any, any>,
    quickSearchCriteria: QuickSearchCriteria
  ): any {
    let _model = model;
    if (!quickSearchCriteria.isEmpty()) {
      if (_model && _model.options.scopes && _model.options.scopes["quickSearch"]) {
        _model = _model.scope({
          method: ["quickSearch", quickSearchCriteria.searchTerms],
        });
      }
    }

    return _model;
  }

  private applyFilters(filterCriteria: FilterCriteria): any {
    let where = undefined;

    if (!filterCriteria.isEmpty()) {
      const filterRoot = filterCriteria.getFilterRoot();

      where = SequelizeParseFilter.parseFilter(filterRoot);
    }

    return {
      where,
    };
  }

  private applyOrder(orderCriteria: OrderCriteria): any {
    let order = [];

    if (!orderCriteria.isEmpty()) {
      const orderCollection = orderCriteria.getOrderCollection();

      order = SequelizeParseOrder.parseOrder(orderCollection);
    }

    return {
      order,
    };
  }

  public generateQuery(props: {
    model: ModelDefined<any, any>;
    queryCriteria?: IQueryCriteria;
  }): ISequelizeQueryOptions {
    const { model, queryCriteria } = props;

    let _model = model;

    const defaultOptions: any = {
      include: [
        {
          all: true,

          // Ejecutar consultas de forma separada para poder paginar
          separate: true,

          // Poder referenciar cualquier campo de los joins. P.e.: %emailAddresses.value%
          // Al activar esto sale el error: "Only HasMany associations support include.separate"
          // nested: true,

          duplicating: false,
        },
      ],
    };

    let paginateOptions = {};
    let whereOptions = {};
    let orderOptions = {};

    if (queryCriteria) {
      // Paginate
      if (queryCriteria.pagination) {
        paginateOptions = this.applyPagination(queryCriteria.pagination);
      }

      // QuickSearch
      if (queryCriteria.quickSearch) {
        _model = this.applyQuickSearch(_model, queryCriteria.quickSearch);
      }

      // Filters
      if (queryCriteria.filters) {
        whereOptions = this.applyFilters(queryCriteria.filters);
      }

      // Order
      if (queryCriteria.order) {
        orderOptions = this.applyOrder(queryCriteria.order);
      }
    }

    return {
      model: _model,
      query: {
        ...defaultOptions,
        ...paginateOptions,
        ...whereOptions,
        ...orderOptions,
      },
    };
  }
}
