import { Service, Inject } from 'typedi';
import { Request, Response, NextFunction } from 'express';
import { castArray, isEmpty } from 'lodash';
import { ServiceError } from '@/exceptions';
import { DynamicFilter } from '@/lib/DynamicFilter';
import {
  IDynamicListFilter,
  IDynamicListService,
  IFilterRole,
  IModel,
} from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import DynamicListFilterRoles from './DynamicListFilterRoles';
import DynamicListSortBy from './DynamicListSortBy';
import DynamicListCustomView from './DynamicListCustomView';
import DynamicListSearch from './DynamicListSearch';

@Service()
export default class DynamicListService implements IDynamicListService {
  @Inject()
  tenancy: TenancyService;

  @Inject()
  dynamicListFilterRoles: DynamicListFilterRoles;

  @Inject()
  dynamicListSortBy: DynamicListSortBy;

  @Inject()
  dynamicListView: DynamicListCustomView;

  @Inject()
  dynamicListSearch: DynamicListSearch;

  /**
   * Parses filter DTO.
   * @param {IMode} model -
   * @param {} filterDTO -
   */
  private parseFilterObject = (model, filterDTO) => {
    return {
      // Merges the default properties with filter object.
      ...(model.defaultSort
        ? {
            sortOrder: model.defaultSort.sortOrder,
            columnSortBy: model.defaultSort.sortOrder,
          }
        : {}),
      ...filterDTO,
    };
  };

  /**
   * Dynamic listing.
   * @param {number} tenantId - Tenant id.
   * @param {IModel} model - Model.
   * @param {IDynamicListFilter} filter - Dynamic filter DTO.
   */
  public dynamicList = async (
    tenantId: number,
    model: IModel,
    filter: IDynamicListFilter
  ) => {
    const dynamicFilter = new DynamicFilter(model);

    // Parses the filter object.
    const parsedFilter = this.parseFilterObject(model, filter);

    // Search by keyword.
    if (filter.searchKeyword) {
      const dynamicListSearch = this.dynamicListSearch.dynamicSearch(
        model,
        filter.searchKeyword
      );
      dynamicFilter.setFilter(dynamicListSearch);
    }
    // Custom view filter roles.
    if (filter.viewSlug) {
      const dynamicListCustomView =
        await this.dynamicListView.dynamicListCustomView(
          dynamicFilter,
          filter.viewSlug,
          tenantId
        );
      dynamicFilter.setFilter(dynamicListCustomView);
    }
    // Sort by the given column.
    if (parsedFilter.columnSortBy) {
      const dynmaicListSortBy = this.dynamicListSortBy.dynamicSortBy(
        model,
        parsedFilter.columnSortBy,
        parsedFilter.sortOrder
      );
      dynamicFilter.setFilter(dynmaicListSortBy);
    }
    // Filter roles.
    if (!isEmpty(parsedFilter.filterRoles)) {
      const dynamicFilterRoles = this.dynamicListFilterRoles.dynamicList(
        model,
        parsedFilter.filterRoles
      );
      dynamicFilter.setFilter(dynamicFilterRoles);
    }
    return dynamicFilter;
  };

  /**
   * Middleware to catch services errors
   * @param {Error} error
   * @param {Request} req
   * @param {Response} res
   * @param {NextFunction} next
   */
  public handlerErrorsToResponse(
    error: Error,
    req: Request,
    res: Response,
    next: NextFunction
  ) {
    if (error instanceof ServiceError) {
      if (error.errorType === 'sort_column_not_found') {
        return res.boom.badRequest(null, {
          errors: [
            {
              type: 'SORT.COLUMN.NOT.FOUND',
              message: 'Sort column not found.',
              code: 200,
            },
          ],
        });
      }
      if (error.errorType === 'view_not_found') {
        return res.boom.badRequest(null, {
          errors: [
            {
              type: 'CUSTOM.VIEW.NOT.FOUND',
              message: 'Custom view not found.',
              code: 100,
            },
          ],
        });
      }
      if (error.errorType === 'filter_roles_fields_not_found') {
        return res.boom.badRequest(null, {
          errors: [
            {
              type: 'FILTER.ROLES.FIELDS.NOT.FOUND',
              message: 'Filter roles fields not found.',
              code: 300,
            },
          ],
        });
      }
      if (error.errorType === 'stringified_filter_roles_invalid') {
        return res.boom.badRequest(null, {
          errors: [
            {
              type: 'STRINGIFIED_FILTER_ROLES_INVALID',
              message: 'Stringified filter roles json invalid.',
              code: 400,
            },
          ],
        });
      }
    }
    next(error);
  }

  /**
   * Parses stringified filter roles.
   * @param {string} stringifiedFilterRoles - Stringified filter roles.
   */
  public parseStringifiedFilter = (filterRoles: IDynamicListFilter) => {
    return {
      ...filterRoles,
      filterRoles: filterRoles.stringifiedFilterRoles
        ? castArray(JSON.parse(filterRoles.stringifiedFilterRoles))
        : [],
    };
  };
}
