function extractFindOptions(req, res, targetAttributes) {
    const result = {};

    result.params = (req && req.params) ? req.params : null;

    if (res && res.locals) {
        Object.keys(res.locals).forEach(key => {
            result[key] = res.locals[key]
        })
    }

    return result;
}

function parseFindOptions(findOptions, targetAttributes) {
    const result = {};

    // Where
    if (findOptions.params) {
        result.where = findOptions.params
    }

    // Paginate
    if (findOptions.paginate) {
        result.page = findOptions.paginate.page;
        result.limit = findOptions.paginate.limit;
    }

    // Order
    if (findOptions.sort) {
        result.order = [];
        Object.keys(findOptions.sort).forEach(key => {
            let dir = findOptions.sort[key] ? 'ASC' : 'DESC';
            result.order.push([key, dir])
        });
    }

    // Attributes
    if (findOptions.fields) {
        if (findOptions.fields.validFields) {
            result.attributes = findOptions.fields.validFields
        }
        if (findOptions.fields.invalidFields && Array.isArray(findOptions.fields.invalidFields)) {
            result.attributes = targetAttributes.filter(attr => !findOptions.fields.invalidFields.includes(attr));
        }
    }

    return result;
}


function checkIsModel(target) {
    return !!target.getTableName;
}

function checkIsAssociation(target) {
    return !!target.associationType;
}

function resultIsAList(params) {
    const _params = Object.keys(params);
    if (_params && Array.isArray(_params) && model.primaryKeyAttributes && Array.isArray(model.primaryKeyAttributes)) {
        return !model.primaryKeyAttributes.every(field => _params.includes(field));
    } else {
        return true;
    }
}

function _find(modelMaybeThunk, options = {}) {
    /*if (typeof modelMaybeThunk != 'function' && !checkIsModel(modelMaybeThunk) && !checkIsAssociation(modelMaybeThunk)) {
        throw new Error('resolverFactory should be called with a model, an association or a function (which resolves to a model or an association)');
    }*/

    if (options.before === undefined) options.before = (options) => options;
    if (options.after === undefined) options.after = (result) => result;

    return async function (req, res, next) {
        let target = typeof modelMaybeThunk === 'function' && !checkIsModel(modelMaybeThunk) ?
            await Promise.resolve(modelMaybeThunk(req, res, next)) : modelMaybeThunk;

        let isModel = checkIsModel(target),
            isAssociation = checkIsAssociation(target),
            association = isAssociation && target,
            isList = options.list || resultIsAList(req.params),
            model = isAssociation && target.target || isModel && target;

        let findOptions = extractFindOptions(req, res);
        let targetAttributes = Object.keys(model.rawAttributes);

        findOptions = parseFindOptions(findOptions, targetAttributes);

        return Promise.resolve(options.before(findOptions, req, res))
            .then(findOptions => {
                return model[isList ? 'fetchAll' : 'fetch'](findOptions);
            })
            .then(result => {
                return options.after(result, req, res);
            });
    }
}


const defaultOptions = {
    //before: optimizeAttributes,
}


const find = (model, options = defaultOptions) => {
    return async function (req, res, next) {
        return _find(model, options)(req, res, next)
    }
}


module.exports = find;