/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ambari.server.proxy;

import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;

import org.apache.ambari.annotations.ApiIgnore;
import org.apache.ambari.server.controller.internal.URLStreamProvider;
import org.apache.ambari.server.view.ImpersonatorSettingImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;

@Path("/")
public class ProxyService {

  public static final int URL_CONNECT_TIMEOUT = 20000;
  public static final int URL_READ_TIMEOUT = 15000;
  public static final int HTTP_ERROR_RANGE_START = Response.Status.BAD_REQUEST.getStatusCode();

  private static final String REQUEST_TYPE_GET = "GET";
  private static final String REQUEST_TYPE_POST = "POST";
  private static final String REQUEST_TYPE_PUT = "PUT";
  private static final String REQUEST_TYPE_DELETE = "DELETE";
  private static final String QUERY_PARAMETER_URL = "url=";
  private static final String AMBARI_PROXY_PREFIX = "AmbariProxy-";
  private static final String ERROR_PROCESSING_URL = "Error occurred during processing URL ";
  private static final String INVALID_PARAM_IN_URL = "Invalid query params found in URL ";

  private final static Logger LOG = LoggerFactory.getLogger(ProxyService.class);

  @GET @ApiIgnore // until documented
  public Response processGetRequestForwarding(@Context HttpHeaders headers, @Context UriInfo ui) {
    return handleRequest(REQUEST_TYPE_GET, ui, null, headers);
  }

  @POST @ApiIgnore // until documented
  @Consumes({MediaType.WILDCARD, MediaType.TEXT_PLAIN, MediaType.TEXT_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON})
  public Response processPostRequestForwarding(InputStream body, @Context HttpHeaders headers, @Context UriInfo ui) {
    return handleRequest(REQUEST_TYPE_POST, ui, body, headers);
  }

  @PUT @ApiIgnore // until documented
  @Consumes({MediaType.WILDCARD, MediaType.TEXT_PLAIN, MediaType.TEXT_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON})
  public Response processPutRequestForwarding(InputStream body, @Context HttpHeaders headers, @Context UriInfo ui) {
    return handleRequest(REQUEST_TYPE_PUT, ui, body, headers);
  }

  @DELETE @ApiIgnore // until documented
  public Response processDeleteRequestForwarding(@Context HttpHeaders headers, @Context UriInfo ui) {
    return handleRequest(REQUEST_TYPE_DELETE, ui, null, headers);
  }

  private Response handleRequest(String requestType, UriInfo ui, InputStream body, HttpHeaders headers) {
    URLStreamProvider urlStreamProvider = new URLStreamProvider(URL_CONNECT_TIMEOUT,
                                                URL_READ_TIMEOUT, null, null, null);
    String query = ui.getRequestUri().getQuery();
    if (query != null && query.indexOf(QUERY_PARAMETER_URL) != -1) {
      String url = query.replaceFirst(QUERY_PARAMETER_URL, "");

      MultivaluedMap<String, String> m = ui.getQueryParameters();
      if (m.containsKey(ImpersonatorSettingImpl.DEFAULT_DO_AS_PARAM)) {              // Case doesn't matter
        LOG.error(INVALID_PARAM_IN_URL + url);
        return Response.status(Response.Status.BAD_REQUEST.getStatusCode()).type(MediaType.TEXT_PLAIN).
            entity(INVALID_PARAM_IN_URL).build();
      }
      
      try {
        HttpURLConnection connection = urlStreamProvider.processURL(url, requestType, body, getHeaderParamsToForward(headers));
        int responseCode = connection.getResponseCode();
        InputStream resultInputStream = null;
        if (responseCode >= HTTP_ERROR_RANGE_START) {
          resultInputStream = connection.getErrorStream();
        } else {
          resultInputStream = connection.getInputStream();
        }
        String contentType = connection.getContentType();
        Response.ResponseBuilder rb = Response.status(responseCode);
        if (contentType.indexOf(APPLICATION_JSON) != -1) {
          rb.entity(new Gson().fromJson(new InputStreamReader(resultInputStream), Map.class));
        } else {
          rb.entity(resultInputStream);
        }
        return rb.type(contentType).build();
      } catch (IOException e) {
        LOG.error(ERROR_PROCESSING_URL + url, e);
        return Response.status(Response.Status.BAD_REQUEST.getStatusCode()).type(MediaType.TEXT_PLAIN).
               entity(e.getMessage()).build();
      }
    }
    return null;
  }

  private Map<String, List<String>> getHeaderParamsToForward(HttpHeaders headers) {
    Map<String, List<String>> headerParamsToForward = new HashMap<>();
    for (String paramName: headers.getRequestHeaders().keySet()) {
      if (paramName.startsWith(AMBARI_PROXY_PREFIX)) {
        headerParamsToForward.put(paramName.replaceAll(AMBARI_PROXY_PREFIX,""), headers.getRequestHeader(paramName));
      }
    }
    return headerParamsToForward;
  }

}
