/*
 * 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.storm.daemon.logviewer.utils;

import static j2html.TagCreator.body;
import static j2html.TagCreator.h2;
import static org.apache.commons.text.StringEscapeUtils.escapeHtml4;

import com.codahale.metrics.Meter;
import com.google.common.io.ByteStreams;

import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.StreamingOutput;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;

import org.apache.storm.daemon.common.JsonResponseBuilder;
import org.apache.storm.daemon.ui.UIHelpers;

public class LogviewerResponseBuilder {

    private LogviewerResponseBuilder() {
    }

    /**
     * Build a Response object representing success response with HTML entity.
     *
     * @param content HTML entity content, String type
     */
    public static Response buildSuccessHtmlResponse(String content) {
        return Response.status(jakarta.ws.rs.core.Response.Status.OK).entity(content)
                .type(MediaType.TEXT_HTML_TYPE).build();
    }

    /**
     * Build a Response object representing success response with JSON entity.
     *
     * @param entity entity object to represent it as JSON
     * @param callback callbackParameterName for JSONP
     * @param origin origin
     */
    public static Response buildSuccessJsonResponse(Object entity, String callback, String origin) {
        return new JsonResponseBuilder().setData(entity).setCallback(callback)
                .setHeaders(LogviewerResponseBuilder.getHeadersForSuccessResponse(origin)).build();
    }

    /**
     * Build a Response object representing download a file.
     *
     * @param contentDispositionName The name to set in the Content-Disposition header
     * @param file file to download
     */
    public static Response buildDownloadFile(String contentDispositionName,
        File file, Meter numFileDownloadExceptions) throws IOException {
        try {
            // do not close this InputStream in method: it will be used from jetty server
            InputStream is = Files.newInputStream(file.toPath());
            return Response.status(jakarta.ws.rs.core.Response.Status.OK)
                    .entity(wrapWithStreamingOutput(is))
                    .type(MediaType.APPLICATION_OCTET_STREAM_TYPE)
                    .header("Content-Disposition", "attachment; filename=\"" + contentDispositionName + "\"")
                    .build();
        } catch (IOException e) {
            numFileDownloadExceptions.mark();
            throw e;
        }
    }

    /**
     * Build a Response object representing unauthorized user, with HTML response.
     *
     * @param user username
     */
    public static Response buildResponseUnauthorizedUser(String user) {
        String entity = buildUnauthorizedUserHtml(user);
        return Response.status(jakarta.ws.rs.core.Response.Status.FORBIDDEN)
                .entity(entity)
                .type(MediaType.TEXT_HTML_TYPE)
                .build();
    }

    /**
     * Build a Response object representing page not found.
     */
    public static Response buildResponsePageNotFound() {
        return Response.status(404)
                .entity("Page not found")
                .type(MediaType.TEXT_HTML_TYPE)
                .build();
    }

    /**
     * Build a Response object representing unauthorized user, with JSON response.
     *
     * @param user username
     * @param callback callbackParameterName for JSONP
     */
    public static Response buildUnauthorizedUserJsonResponse(String user, String callback) {
        return new JsonResponseBuilder().setData(UIHelpers.unauthorizedUserJson(user))
                .setCallback(callback).setStatus(403).build();
    }

    /**
     * Build a Response object representing exception, with JSON response.
     *
     * @param ex Exception object
     * @param callback callbackParameterName for JSONP
     */
    public static Response buildExceptionJsonResponse(Exception ex, String callback) {
        int statusCode = 500;
        return new JsonResponseBuilder().setData(UIHelpers.exceptionToJson(ex, statusCode))
                .setCallback(callback).setStatus(statusCode).build();
    }

    private static Map<String, Object> getHeadersForSuccessResponse(String origin) {
        Map<String, Object> headers = new HashMap<>();
        headers.put("Access-Control-Allow-Origin", origin);
        headers.put("Access-Control-Allow-Credentials", "true");
        return headers;
    }

    private static String buildUnauthorizedUserHtml(String user) {
        String content = "User '" + escapeHtml4(user) + "' is not authorized.";
        return body(h2(content)).render();
    }

    private static StreamingOutput wrapWithStreamingOutput(final InputStream inputStream) {
        return os -> {
            OutputStream wrappedOutputStream = os;
            if (!(os instanceof BufferedOutputStream)) {
                wrappedOutputStream = new BufferedOutputStream(os);
            }

            ByteStreams.copy(inputStream, wrappedOutputStream);

            wrappedOutputStream.flush();
        };
    }

}
