// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-or-later

/**
 * @file presentation.cpp
 * @brief Layer 6 (Presentation) - wires the transport ring buffer to the HTTP parser.
 *
 * This file is now a thin adapter.  The HTTP parsing logic lives in
 * http_parser.cpp.  This layer only knows about:
 *   - The transport RX read API (det_conn_available / det_conn_read_byte) - it
 *     never indexes the ring itself; transport owns rx_buffer / rx_head / rx_tail
 *   - Slot-indexed helpers that the session and application layers expect
 *
 * The slot-indexed `http_reset()` pre-stamps slot_id then delegates to
 * `http_parser_reset()`.  The slot-indexed `http_parse()` drains all
 * available bytes from the ring buffer through `http_parser_feed()` and
 * stops as soon as the parser reaches any terminal state.
 */

#include "presentation.h"
#include "network_drivers/session/proto_handler.h" // ProtoHandler (the L5 dispatch seam this registers into)
#if DETWS_ENABLE_WEBSOCKET
#include "network_drivers/presentation/websocket/websocket.h" // ws_find(): a WS-upgraded slot must never be HTTP-parsed
#endif
#if DETWS_ENABLE_TLS
#include "network_drivers/tls/tls.h"
#if DETWS_ENABLE_HTTP2
#include "network_drivers/presentation/http2/h2_server.h"
#endif
#include <string.h> // strcmp (ALPN check)
#endif

#if DETWS_ENABLE_KEEPALIVE
uint16_t http_req_count[MAX_CONNS];
#endif

void http_reset(uint8_t slot_id)
{
    if (slot_id >= MAX_CONNS)
        return;
    http_pool[slot_id].slot_id = slot_id; // ensure slot_id is correct before reset reads it
    http_parser_reset(&http_pool[slot_id]);
}

void http_conn_open(uint8_t slot_id)
{
    if (slot_id >= MAX_CONNS)
        return;
#if DETWS_ENABLE_KEEPALIVE
    http_req_count[slot_id] = 0; // fresh connection: clear the keep-alive request tally
#endif
    http_reset(slot_id);
}

void http_parse(uint8_t slot_id)
{
    if (slot_id >= MAX_CONNS)
        return;

#if DETWS_ENABLE_WEBSOCKET
    // Once a slot upgrades to WebSocket its rx ring carries WS frames, not HTTP.
    // The WS frame parser is pumped separately (handle()/the worker loop); feeding
    // those bytes to the HTTP parser here would consume - and corrupt - the first
    // WS frame. This guard makes "never HTTP-parse a WS slot" hold for every caller
    // (the event-queue dispatch raced the WS pump and ate the first frame's header
    // byte, dropping the first connection after a reboot).
    if (ws_find(slot_id))
        return;
#endif

    HttpReq *req = &http_pool[slot_id];

    // Drain via the transport read API - the parser never touches the ring itself.
    // Check the terminal state BEFORE consuming so a pipelined next request is left
    // in the ring; the window is reopened by the worker's det_conn_ack_consumed().
    while (det_conn_available(slot_id) > 0)
    {
        switch (req->parse_state)
        {
        case PARSE_COMPLETE:
        case PARSE_ERROR:
        case PARSE_ENTITY_TOO_LARGE:
        case PARSE_URI_TOO_LONG:
            return; // terminal state - drain nothing further
        default:
            break;
        }

        uint8_t byte = 0;
        if (!det_conn_read_byte(slot_id, &byte)) // ring drained between available() and here
            break;
        http_parser_feed(req, byte);
    }
}

// ---------------------------------------------------------------------------
// HTTP ProtoHandler - the L5 dispatch seam for an HTTP connection.
//
// This is where an HTTP connection is fed: the plaintext path drains the ring
// through http_parse() (above); the TLS path drives the handshake, then routes
// decrypted bytes to the HTTP/2 engine (ALPN "h2"), the WebSocket pump (an
// upgraded slot), or the HTTP/1.1 parser. Keeping it here (Layer 6, with the rest
// of the HTTP-connection glue) leaves the session layer's dispatcher free of any
// HTTP / TLS / h2 / ws specifics - it only routes events to registered handlers.
// ---------------------------------------------------------------------------

#if DETWS_ENABLE_TLS
// Abort a TLS connection (fatal handshake/read error). det_conn_abort_slot owns
// the whole teardown: free the TLS context (abrupt), detach the pcb, reset the
// slot, then RST - so this never reaches into the raw tcp_pcb.
static void tls_abort(uint8_t slot)
{
    det_conn_abort_slot(slot);
    http_reset(slot);
}

// Pump a TLS connection: drive the handshake to completion, then decrypt any
// application data straight into the HTTP parser (same byte-by-byte feed the
// plaintext path uses; the rx ring now holds ciphertext, consumed by the BIO).
static void tls_data(uint8_t slot)
{
    if (!det_tls_established(slot))
    {
        int h = det_tls_handshake(slot);
        if (h < 0)
        {
            tls_abort(slot);
            return;
        }
        if (h == 0)
            return; // still handshaking; wait for more ciphertext
    }

#if DETWS_ENABLE_HTTP2
    // Just past the handshake: if the client negotiated ALPN "h2", this connection speaks HTTP/2
    // for its lifetime - hand its decrypted bytes to the h2 engine, not the HTTP/1.1 parser.
    if (!conn_pool[slot].h2_checked)
    {
        conn_pool[slot].h2_checked = 1;
        const char *alpn = det_tls_alpn(slot);
        if (alpn && strcmp(alpn, "h2") == 0)
        {
            conn_pool[slot].h2 = 1;
            conn_pool[slot].resp_sink = h2_server_respond; // route responses through the h2 framer
            h2_server_open(slot);
        }
    }
    if (conn_pool[slot].h2)
    {
        h2_server_data(slot);
        return;
    }
#endif

#if DETWS_ENABLE_WEBSOCKET
    // A TLS slot upgraded to WebSocket is pumped from handle() (it decrypts
    // records and feeds the WS frame parser, dispatching each frame); leave the
    // ciphertext in the rx ring for it rather than feeding the HTTP parser here.
    if (ws_find(slot))
        return;
#endif

    uint8_t buf[256];
    int n;
    while ((n = det_tls_read(slot, buf, sizeof(buf))) > 0)
    {
        HttpReq *req = &http_pool[slot];
        for (int i = 0; i < n; i++)
        {
            if (req->parse_state == PARSE_COMPLETE || req->parse_state == PARSE_ERROR ||
                req->parse_state == PARSE_ENTITY_TOO_LARGE || req->parse_state == PARSE_URI_TOO_LONG)
                break; // terminal state - let handle() dispatch before reading more
            http_parser_feed(req, buf[i]);
        }
    }
    if (n < 0)
        tls_abort(slot);
}
#endif // DETWS_ENABLE_TLS

// The data/close paths branch on TLS (a TLS slot's rx ring holds ciphertext,
// decrypted into the parser); accept maps directly.
static void http_evt_accept(uint8_t slot)
{
    http_conn_open(slot); // resets the parser + (keep-alive) the per-conn request tally
#if DETWS_ENABLE_HTTP2
    conn_pool[slot].h2 = 0; // a reused slot must re-run the post-handshake ALPN check
    conn_pool[slot].h2_checked = 0;
    conn_pool[slot].resp_sink = nullptr; // back to the HTTP/1.1 builder until ALPN says otherwise
#endif
}
static void http_evt_data(uint8_t slot)
{
#if DETWS_ENABLE_TLS
    if (conn_pool[slot].tls)
    {
        tls_data(slot);
        return;
    }
#endif
    http_parse(slot); // a no-op once the slot has upgraded to WebSocket (see http_parse)
}
static void http_evt_close(uint8_t slot)
{
#if DETWS_ENABLE_TLS
    if (conn_pool[slot].tls)
        det_tls_conn_free(slot); // also covers timeouts (EVT_ERROR)
#endif
    http_reset(slot);
}
// HTTP's poll pump is instance-bound (it dispatches into a DetWebServer's routes), so the routing
// core installs it here at begin() via http_proto_set_poll(). The trampoline lets the ProtoHandler
// stay a plain static const while the actual pump lives in the application TU - the on_poll analogue
// of the resp_sink TX seam. Until installed (e.g. the native harness before begin()) it is a no-op.
static void (*s_http_poll)(uint8_t slot) = nullptr;
static void http_evt_poll(uint8_t slot)
{
    if (s_http_poll)
        s_http_poll(slot);
}
void http_proto_set_poll(void (*fn)(uint8_t slot))
{
    s_http_poll = fn;
}

static const ProtoHandler s_http_handler = {http_evt_accept, http_evt_data, http_evt_close, http_evt_poll};

const ProtoHandler *http_proto_handler(void)
{
    return &s_http_handler;
}
