package org.apache.helix.rest.server.filters;

/*
 * 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.
 */

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;

import com.google.common.io.CharStreams;
import org.apache.helix.rest.common.HelixRestUtils;
import org.apache.helix.rest.server.auditlog.AuditLog;
import org.apache.helix.rest.server.auditlog.AuditLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Provider
@PreMatching
public class AuditLogFilter implements ContainerRequestFilter, ContainerResponseFilter {
  private static Logger _logger = LoggerFactory.getLogger(AuditLogFilter.class.getName());

  @Context
  private HttpServletRequest _servletRequest;

  private List<AuditLogger> _auditLoggers;

  public AuditLogFilter(List<AuditLogger> auditLoggers) {
    _auditLoggers = auditLoggers;
  }

  @Override
  public void filter(ContainerRequestContext request) throws IOException {
    AuditLog.Builder auditLogBuilder = new AuditLog.Builder();

    auditLogBuilder.namespace(getNamespace())
        .requestPath(request.getUriInfo().getPath())
        .httpMethod(request.getMethod())
        .startTime(new Date())
        .requestHeaders(getHeaders(request.getHeaders()))
        .principal(_servletRequest.getUserPrincipal())
        .clientIP(_servletRequest.getRemoteAddr())
        .clientHostPort(_servletRequest.getRemoteHost() + ":" + _servletRequest.getRemotePort());

    String entity = getEntity(request.getEntityStream());
    auditLogBuilder.requestEntity(entity);

    InputStream stream = new ByteArrayInputStream(entity.getBytes(StandardCharsets.UTF_8));
    request.setEntityStream(stream);

    request.setProperty(AuditLog.ATTRIBUTE_NAME, auditLogBuilder);
  }

  @Override
  public void filter(ContainerRequestContext request, ContainerResponseContext response)
      throws IOException {
    AuditLog.Builder auditLogBuilder;
    try {
      auditLogBuilder = (AuditLog.Builder) request.getProperty(AuditLog.ATTRIBUTE_NAME);
      auditLogBuilder.completeTime(new Date()).responseCode(response.getStatus());
      Object entity = response.getEntity();
      if(entity != null && entity instanceof String) {
        auditLogBuilder.responseEntity((String) response.getEntity());
      }

      AuditLog auditLog = auditLogBuilder.build();
      if (_auditLoggers != null) {
        for (AuditLogger logger : _auditLoggers) {
          logger.write(auditLog);
        }
      }
    } catch (Exception ex) {
      _logger.error("Failed to add audit log " + ex);
    }
  }

  private List<String> getHeaders(MultivaluedMap<String, String> headersMap) {
    List<String> headers = new ArrayList<>();
    for (String key : headersMap.keySet()) {
      headers.add(key + ":" + headersMap.get(key));
    }

    return headers;
  }

  private String getEntity(InputStream entityStream) {
    if (entityStream != null) {
      try {
        return CharStreams.toString(new InputStreamReader(entityStream, StandardCharsets.UTF_8));
      } catch (IOException e) {
        _logger.warn("Failed to parse input entity stream " + e);
      }
    }
    return null;
  }

  private String getNamespace() {
    String servletPath = _servletRequest.getServletPath();
    return HelixRestUtils.getNamespaceFromServletPath(servletPath);
  }

}
