/*
 * Copyright (C) 2022 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */
import {QueryDslQueryContainer} from '@elastic/elasticsearch/lib/api/types.js';
import {SCConfigFile, SCSearchQuery} from '@openstapps/core';
import {ElasticsearchConfig} from '../types/elasticsearch-config.js';
import {buildFilter} from './filter.js';
import {buildScoringFunctions} from './boost/scoring-functions.js';

/**
 * Builds body for Elasticsearch requests
 * @param parameters Parameters for querying the backend
 * @param defaultConfig Default configuration of the backend
 * @param elasticsearchConfig Elasticsearch configuration
 * @returns ElasticsearchQuery (body of a search-request)
 */
export const buildQuery = function buildQuery(
  parameters: SCSearchQuery,
  defaultConfig: SCConfigFile,
  elasticsearchConfig: ElasticsearchConfig,
): QueryDslQueryContainer {
  // if config provides a minMatch parameter, we use query_string instead of a match query
  let query;
  if (elasticsearchConfig.query === undefined) {
    query = {
      query_string: {
        analyzer: 'search_german',
        default_field: 'name',
        minimum_should_match: '90%',
        query: typeof parameters.query === 'string' ? parameters.query : '*',
      },
    };
  } else if (elasticsearchConfig.query.queryType === 'query_string') {
    query = {
      query_string: {
        analyzer: 'search_german',
        default_field: 'name',
        minimum_should_match: elasticsearchConfig.query.minMatch,
        query: typeof parameters.query === 'string' ? parameters.query : '*',
      },
    };
  } else if (elasticsearchConfig.query.queryType === 'dis_max') {
    if (typeof parameters.query === 'string' && parameters.query !== '*') {
      query = {
        dis_max: {
          boost: 1.2,
          queries: [
            {
              match: {
                name: {
                  boost: elasticsearchConfig.query.matchBoosting,
                  fuzziness: elasticsearchConfig.query.fuzziness,
                  query: parameters.query,
                },
              },
            },
            {
              query_string: {
                default_field: 'name',
                minimum_should_match: elasticsearchConfig.query.minMatch,
                query: parameters.query,
              },
            },
          ],
          tie_breaker: elasticsearchConfig.query.tieBreaker,
        },
      };
    }
  } else {
    throw new Error(
      'Unsupported query type. Check your config file and reconfigure your elasticsearch query',
    );
  }

  const functionScoreQuery: QueryDslQueryContainer = {
    function_score: {
      functions: buildScoringFunctions(defaultConfig.internal.boostings, parameters.context),
      query: {
        bool: {
          minimum_should_match: 0, // if we have no should, nothing can match
          must: [],
          should: [],
        },
      },
      score_mode: 'multiply',
    },
  };

  const mustMatch = functionScoreQuery.function_score?.query?.bool?.must;

  if (Array.isArray(mustMatch)) {
    if (query !== undefined) {
      mustMatch.push(query);
    }

    if (parameters.filter !== undefined) {
      mustMatch.push(buildFilter(parameters.filter));
    }
  }

  return functionScoreQuery;
};
