/*
 * Copyright (C) 2025-2026  Tim Zimmermann <tim@aglais.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.aglais.zms.server;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http2.Http2StreamChannel;
import io.netty.handler.codec.quic.QuicStreamChannel;
import org.aglais.zms.db.core.model.User;
import org.aglais.zms.util.Log;
import org.aglais.zms.util.VersionUtil;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

import static org.aglais.zms.server.AglaisHttpAuthenticator.IP_KEY;
import static org.aglais.zms.server.AglaisHttpAuthenticator.USER_KEY;

/**
 * Base class for HTTP request handlers
 */
public abstract class AglaisBaseHttpRequestHandler extends SimpleChannelInboundHandler<HttpRequest> {

    private static final String LOG_TAG = AglaisBaseHttpRequestHandler.class.getSimpleName();

    private final boolean advertiseQuic;

    /**
     * Create a new {@link AglaisBaseHttpRequestHandler} instance
     * <p>
     * <b>For internal use only, this class does nothing by default</b>
     *
     * @param advertiseQuic True if support for QUIC should be advertised using the Alt-Svc header
     */
    public AglaisBaseHttpRequestHandler(boolean advertiseQuic) {
        this.advertiseQuic = advertiseQuic;
    }

    /**
     * Write a response to the channel, respecting keepalive headers
     *
     * @param ctx           The {@link ChannelHandlerContext} used for communication
     * @param request       The original {@link HttpRequest}
     * @param response      The {@link FullHttpResponse}
     * @param contentLength The response content length
     */
    private void accessLog(ChannelHandlerContext ctx, HttpRequest request, FullHttpResponse response,
                           int contentLength) {
        String remoteAddr = ctx.channel().attr(IP_KEY).get();
        User remoteUser = ctx.channel().attr(USER_KEY).get();
        String protocol = ctx.channel() instanceof QuicStreamChannel ? "HTTP/3.0" :
                ctx.channel() instanceof Http2StreamChannel ? "HTTP/2.0" : request.protocolVersion().text();
        String requestStr = String.format("%s %s %s", request.method().name(), request.uri(), protocol);
        int status = response.status().code();
        String httpReferer = request.headers().get(HttpHeaderNames.REFERER, "-");
        String httpUserAgent = request.headers().get(HttpHeaderNames.USER_AGENT, "-");

        String logLine = String.format("%s - %s \"%s\" %d %d \"%s\" \"%s\"", remoteAddr,
                remoteUser != null ? remoteUser.getName() : "-", requestStr, status, contentLength, httpReferer,
                httpUserAgent);

        Log.i(LOG_TAG, logLine);
    }

    /**
     * Write a response to the channel, respecting keepalive headers
     *
     * @param ctx      The {@link ChannelHandlerContext} for writing the response
     * @param request  The original {@link HttpRequest}
     * @param response The {@link FullHttpResponse} to write
     */
    protected final void writeResponse(ChannelHandlerContext ctx, HttpRequest request, FullHttpResponse response) {
        boolean keepAlive = HttpUtil.isKeepAlive(request);
        int contentLength = response.content().readableBytes();

        if (keepAlive) {
            response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, contentLength);
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        if (advertiseQuic) {
            SocketAddress address = ChannelUtil.getLocalAddress(ctx.channel());
            // QUIC is not supported for unix sockets
            if (address instanceof InetSocketAddress inetAddr) {
                response.headers().set(HttpHeaderNames.ALT_SVC, "h3=\":" + inetAddr.getPort() + "\"; ma=86400");
            }
        }
        ctx.write(response);

        if (!keepAlive) {
            ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }

        if (VersionUtil.isDevBuild(VersionUtil.getVersion()))
            accessLog(ctx, request, response, contentLength);
    }

}
