package io.netfoundry.reporting.controller;

import java.io.IOException;
import java.util.UUID;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
import io.netfoundry.auth.clientv2.AuthorizationContext;
import io.netfoundry.auth.clientv2.ResourceResolver;
import io.netfoundry.auth.domainv2.ancestry.ResourceIdPath;
import io.netfoundry.auth.domainv2.resourcetype.ResourceType;
import io.netfoundry.reporting.domain.Network;
import io.netfoundry.reporting.domain.Organization;
import io.netfoundry.reporting.exception.PermissionDeniedAdvice;
import io.netfoundry.reporting.service.ReportingElasticSearchService;
import lombok.extern.slf4j.Slf4j;


@Slf4j
@RestController
public class ReportingElasticSearchController {

  private static final String DOMAIN_NETWORK = "io.netfoundry.network";
  private static final String RESOURCE_TYPE = "network";
  private static final String RESOURCE_ACTION = "read-elastic-search";
  private final RestClient elasticClient;
  private final ResourceResolver resourceResolver;
  private final ReportingElasticSearchService elasticSearchService;

  @Autowired
  public ReportingElasticSearchController(RestClient elasticClient, ResourceResolver resourceResolver,
      ReportingElasticSearchService elasticSearchService) {
    this.elasticClient = elasticClient;
    this.resourceResolver = resourceResolver;
    this.elasticSearchService = elasticSearchService;
  }


  @PostMapping(path = "elastic/{index}/{networkGroupId}/_search/", produces = {MediaType.APPLICATION_JSON_VALUE, "application/json"})
  public Object search(@PathVariable UUID networkGroupId, @RequestParam(name = "networkId", required = false) UUID networkId, @PathVariable String index,
      @RequestBody String query) throws ElasticsearchException, IOException {
    AuthorizationContext authContext = AuthorizationContext.getContext();
    if (authContext == null) {
      throw new PermissionDeniedAdvice.PermissionDeniedException();
    }
    ResourceIdPath.Builder pathBuilder = null;
    ResourceType networkGroupResourceType = resourceResolver.getResourceType(Organization.class);
    if (networkId != null) {
      ResourceType networkResourceType = resourceResolver.getResourceType(Network.class);
      pathBuilder = ResourceIdPath.to(networkResourceType, networkId).withParent(networkGroupResourceType, networkGroupId);
    } else {
      pathBuilder = ResourceIdPath.to(networkGroupResourceType, networkGroupId);
    }
    boolean authorized = authContext.isAuthorized(DOMAIN_NETWORK, RESOURCE_TYPE, RESOURCE_ACTION, pathBuilder.build());
    if (!authorized) {
      throw new PermissionDeniedAdvice.PermissionDeniedException();
    }

    String constrainedQuery = null;
    try {
      constrainedQuery = elasticSearchService.insertConstraints(query, networkGroupId, networkId);
    } catch (IOException e) {
      log.warn("error occurred trying to insert orgId into query - {}", e.getLocalizedMessage());
      throw new IllegalStateException("error while constraining query to " + networkGroupId, e);
    }
    log.info("Search query {}", query);
    // Create a search request
    Request searchRequest = new Request("POST", "/" + index + "-*" + "/_search");
    searchRequest.setJsonEntity(constrainedQuery);
    searchRequest.setOptions(RequestOptions.DEFAULT.toBuilder().addHeader("Content-Type", "application/json").build());
    // Perform the search
    String response = EntityUtils.toString(elasticClient.performRequest(searchRequest).getEntity());
    log.info("Search response {}", response);
    return response;
  }

}
