/*
This middleware is to be used in the controller beforeAction hook.

You can search text, int and multi-column.

Example use in a controller:
query = ['search'];
beforeAction = [
    search(MyModel, {
        searchText: ['description'],
        searchMultiCol: {
            'fullName': ['firstName', 'suffix', 'lastName']
        }
    })
];

Example query:
/my-model?search[fullName]=query&search[description]=word

Where:
 - MyModel is a reference to the model which is to be searched
 - options contains an object with one or more of searchText, searchInt and searchMultiCol
    + searchText is an array with the column names to be searched. String search is substring based and case insensitive.
    + searchInt is an array with the column names to be searched. Integer search is meant for numbers and is left to right.
    + searchMultiCol is an object with as the key a name for the multicol search parameter and as a value an array of the 
      columns to be concatted and then searched in the same manner as normal text search.
    
FilterIds is a small function which intersects the found id's with the id's already present in the filter array (if any).
*/

import snakeCase from 'lodash.snakecase';
import filterIds from 'app/utils/filter-ids';

export default function(model, options){
    const {
        searchText = [],
        searchInt = [],
        searchMultiCol = {},
        forActions = []
        } = options;

    return async (request, response) => {
        const {
            action,
            method,
            params: {
                search
            }
        } = request;

        if(method === 'GET' && (action === 'index' || forActions.includes(action)) && search){
            const keys = Object.keys(search);

            let _searchText = new Map();
            searchText.forEach((key) => {
                if(keys.includes(key)){
                    _searchText.set(snakeCase(key), search[key]);
                }
            });

            let _searchInt = new Map();
            searchInt.forEach((key) => {
                if(keys.includes(key)){
                    _searchInt.set(snakeCase(key), search[key]);
                }
            });

            let _searchMultiCol = new Map();
            Object.keys(searchMultiCol).forEach((key) => {
                if(keys.includes(key)){
                    _searchMultiCol.set(key, search[key]);
                }
            });

            if(_searchText.size || _searchInt.size || _searchMultiCol.size){
                let queryArr = [];
                let queryValues = [];
                _searchText.forEach((value, key) => {
                    queryArr.push(`${key} ILIKE ?`);
                    queryValues.push(`%${value}%`);
                });
                _searchInt.forEach((value, key) => {
                    queryArr.push(`CAST(${key} AS TEXT) LIKE ?`);
                    queryValues.push(`${value}%`);
                });
                _searchMultiCol.forEach((value, key) => {
                    let cols = searchMultiCol[key].map((value) => `coalesce(${snakeCase(value)}, '')`);
                    queryArr.push(cols.join(` || `) + ' ILIKE ?');
                    // remove whitespaces from search string
                    queryValues.push(`%${value.replace(/\s/g, '')}%`);
                });
                let queryString = queryArr.join(' AND ');

                const filterIdsArr = await model.table()
                    .whereRaw(queryString, queryValues)
                    .reduce((idsRes, row) => {
                        idsRes.push(row.id);
                        return idsRes;
                    }, []);

                request.params.filter = filterIds(request, filterIdsArr);
            }
        }
    }
}