/*
 * Licensed to Crate under one or more contributor license agreements.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.  Crate 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.
 *
 * However, if you have executed another commercial license agreement
 * with Crate these terms will supersede the license and you may use the
 * software solely pursuant to the terms of the relevant commercial
 * agreement.
 */

package io.crate.protocols.http;

import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import org.elasticsearch.common.io.FileSystemUtils;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Locale;
import java.util.Map;

import static java.nio.file.Files.readAttributes;

public final class StaticSite {

    public static FullHttpResponse serveSite(Path siteDirectory,
                                             FullHttpRequest request,
                                             ByteBufAllocator alloc) throws IOException {
        if (request.method() != HttpMethod.GET) {
            return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN);
        }
        String sitePath = request.uri();
        while (sitePath.length() > 0 && sitePath.charAt(0) == '/') {
            sitePath = sitePath.substring(1);
        }

        // we default to index.html, or what the plugin provides (as a unix-style path)
        // this is a relative path under _site configured by the plugin.
        if (sitePath.length() == 0) {
            sitePath = "index.html";
        }

        final String separator = siteDirectory.getFileSystem().getSeparator();
        // Convert file separators.
        sitePath = sitePath.replace("/", separator);
        Path file = siteDirectory.resolve(sitePath);

        // return not found instead of forbidden to prevent malicious requests to find out if files exist or don't exist
        if (!Files.exists(file) || FileSystemUtils.isHidden(file) ||
            !file.toAbsolutePath().normalize().startsWith(siteDirectory.toAbsolutePath().normalize())) {

            return Responses.contentResponse(
                HttpResponseStatus.NOT_FOUND, alloc, "Requested file [" + file + "] was not found");
        }

        BasicFileAttributes attributes = readAttributes(file, BasicFileAttributes.class);
        if (!attributes.isRegularFile()) {
            // If it's not a regular file, we send a 403
            final String msg = "Requested file [" + file + "] is not a valid file.";
            return Responses.contentResponse(HttpResponseStatus.NOT_FOUND, alloc, msg);
        }
        try {
            byte[] data = Files.readAllBytes(file);
            var resp = Responses.contentResponse(HttpResponseStatus.OK, alloc, data);
            resp.headers().set(HttpHeaderNames.CONTENT_TYPE, guessMimeType(file.toAbsolutePath().toString()));
            return resp;
        } catch (IOException e) {
            return Responses.contentResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR, alloc, e.getMessage());
        }
    }

    private static String guessMimeType(String path) {
        int lastDot = path.lastIndexOf('.');
        if (lastDot == -1) {
            return "";
        }
        String extension = path.substring(lastDot + 1).toLowerCase(Locale.ROOT);
        return DEFAULT_MIME_TYPES.getOrDefault(extension, "");
    }

    private static final Map<String, String> DEFAULT_MIME_TYPES = Map.ofEntries(
        Map.entry("txt", "text/plain"),
        Map.entry("css", "text/css"),
        Map.entry("csv", "text/csv"),
        Map.entry("htm", "text/html"),
        Map.entry("html", "text/html"),
        Map.entry("xml", "text/xml"),
        Map.entry("js", "text/javascript"), // Technically it should be application/javascript (RFC 4329), but IE8 struggles with that
        Map.entry("xhtml", "application/xhtml+xml"),
        Map.entry("json", "application/json"),
        Map.entry("pdf", "application/pdf"),
        Map.entry("zip", "application/zip"),
        Map.entry("tar", "application/x-tar"),
        Map.entry("gif", "image/gif"),
        Map.entry("jpeg", "image/jpeg"),
        Map.entry("jpg", "image/jpeg"),
        Map.entry("tiff", "image/tiff"),
        Map.entry("tif", "image/tiff"),
        Map.entry("png", "image/png"),
        Map.entry("svg", "image/svg+xml"),
        Map.entry("ico", "image/vnd.microsoft.icon"),
        Map.entry("mp3", "audio/mpeg")
    );
}