package io.netfoundry.reporting.service;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import co.elastic.clients.json.JsonData;
import io.netfoundry.reporting.domain.ReportingSearchRequest;
import io.netfoundry.reporting.util.ReportingTemplateEnum;
import io.netfoundry.reporting.util.ReportingConstants;

@Service
public class ReportingElasticSearchServiceWithActions {
  private static Map<ReportingTemplateEnum, Function<ReportingSearchRequest,Map<String, JsonData>>> searchTemplateActions = new HashMap<>();
  static {
    // Define the map of functions
    searchTemplateActions.put(ReportingTemplateEnum.NW_UTILIZATION_ACTIVE_ENDPOINTS_TMPL,
        ReportingElasticSearchServiceWithActions::handleNwUtilizationActiveEndpointsAction);



  }

  public String insertConstraints(String queryString, UUID networkGroupId, @Nullable UUID networkId) throws IOException {
    ObjectMapper om = new ObjectMapper();
    JsonNode queryTree = om.readTree(queryString);
    // query.bool.must[]
    JsonNode queryNode = null;
    if (queryTree.has(ReportingConstants.QUERY)) {
      queryNode = queryTree.get(ReportingConstants.QUERY);
    } else {
      throw new IllegalArgumentException("query string does not contain a 'query' node.");
    }
    JsonNode boolNode = null;
    if (queryNode.has("bool")) {
      boolNode = queryNode.get("bool");

    } else {
      throw new IllegalArgumentException("query string does not contain a 'query.bool' node.");
    }
    JsonNode mustNode = null;
    if (boolNode.has("must")) {
      mustNode = boolNode.get("must");
    } else {
      throw new IllegalArgumentException("query string does not contain a 'query.bool.must' node.");
    }
    ArrayNode mustArray = null;
    if (mustNode.isArray()) {
      mustArray = (ArrayNode) mustNode;
    } else {
      throw new IllegalArgumentException("array expected at 'query.bool.must'.");
    }

    // restrict the user query based on the network group id they specified in the path
    insertMatchPhraseTerm(mustArray, "organizationId", networkGroupId);

    // restrict the user query based on the network id they specified (optional)
    if (networkId != null) {
      insertMatchPhraseTerm(mustArray, "networkId", networkId);
    }

    return om.writeValueAsString(queryTree);
  }

  /**
   * This will insert (as in add to existing; not replace) a new "match_phrase" style query constraint
   * into the passed in ArrayNode. It is assumed that the passed in ArrayNode is the "must" array from
   * the ES query. If that is the case, this will result in restricting the query that the user
   * defined to the subset of results that also match the passed in id in the passed in field name. If
   * a user submits a query for results outside of the injected constraints, they will not get those
   * results because their query constraint and this injected aspect are AND'd together via the "must"
   * aspect of the query.
   */
  private static void insertMatchPhraseTerm(ArrayNode mustArray, String fieldName, UUID id) {
    JsonNodeFactory factory = JsonNodeFactory.instance;
    ObjectNode query = factory.objectNode();
    query.put(ReportingConstants.QUERY, id.toString());
    ObjectNode organizationId = factory.objectNode();
    organizationId.set(fieldName, query);
    ObjectNode matchPhrase = factory.objectNode();
    matchPhrase.set("match_phrase", organizationId);
    mustArray.add(matchPhrase);
  }

  public Map<String, JsonData> getSearchTemplateParamMap(UUID organizationId, ReportingSearchRequest request) {
    Map<String, JsonData> params = null;
    if (searchTemplateActions.containsKey(request.getTemplateEnum())) {
      searchTemplateActions.get(request.getTemplateEnum()).apply(request);

    } else {
      System.out.println("Invalid case number");
    }

    return params;

  }

  private static Map<String, JsonData> handleNwUtilizationActiveEndpointsAction(ReportingSearchRequest request) {
    Map<String, JsonData> params =  new HashMap<>();
    // params.put(ReportingConstants.ORGANIZATIONID_QUERY_PARAM, JsonData.of(organizationId));
    params.put(ReportingConstants.NETWORK_ID_QUERY_PARAM, JsonData.of(request.getNetworkId()));
    params.put(ReportingConstants.TIMESTAMP_GREATER_THAN_EQUAL_QUERY_PARAM, JsonData.of(request.getGte()));
    params.put(ReportingConstants.TIMESTAMP_LESSER_THAN_EQUAL_QUERY_PARAM, JsonData.of(request.getLte()));
    return params;
  }

}
