// tls_server.cpp — OpenSSL memory-BIOs pumped over uv_tcp_t + llhttp.
//
// This is the genuinely new piece compared to the Node.js original (~300 lines).
// Pattern: accept TCP -> SSL_new with mem BIOs -> uv_read_start feeds encrypted
// bytes into rbio -> SSL_read drains plaintext into llhttp -> SSL_write puts
// response plaintext into wbio -> flush wbio to socket.

#include "tls_server.hpp"
#include "log.hpp"

#include <cstring>

// ---------------------------------------------------------------------------
// Globals
// ---------------------------------------------------------------------------

static uv_loop_t*  g_loop = nullptr;
static SSL_CTX*    g_ssl_ctx = nullptr;
static uv_tcp_t    g_server{};
static uv_signal_t g_sigint{};
static uv_signal_t g_sigterm{};
static void (*g_on_request)(tls_session*) = nullptr;

// ---------------------------------------------------------------------------
// Forward declarations for signal shutdown
// ---------------------------------------------------------------------------

static void tls_server_shutdown();

// ---------------------------------------------------------------------------
// SSL error logging helper
// ---------------------------------------------------------------------------

static void ssl_log_errors(const char* ctx) {
    unsigned long e;
    while ((e = ERR_get_error()) != 0) {
        char buf[256];
        ERR_error_string_n(e, buf, sizeof(buf));
        logc::warn("[%s] SSL: %s", ctx, buf);
    }
}

// ---------------------------------------------------------------------------
// Encrypted data flush: wbio -> socket
// ---------------------------------------------------------------------------

static void flush_encrypted(tls_session* sess) {
    char buf[16384];
    for (;;) {
        int n = BIO_read(sess->wbio, buf, sizeof(buf));
        if (n <= 0) break;

        auto* req = new uv_write_t{};
        char* data = new char[n];
        std::memcpy(data, buf, n);
        uv_buf_t wbuf = uv_buf_init(data, n);
        req->data = data;

        uv_write(req, reinterpret_cast<uv_stream_t*>(&sess->tcp_handle),
                 &wbuf, 1, [](uv_write_t* r, int) {
                     delete[] static_cast<char*>(r->data);
                     delete r;
                 });
    }
}

// ---------------------------------------------------------------------------
// Drain SSL plaintext -> llhttp
// ---------------------------------------------------------------------------

static void drain_ssl_to_llhttp(tls_session* sess) {
    char buf[16384];
    for (;;) {
        int n = SSL_read(sess->ssl, buf, sizeof(buf));
        if (n <= 0) break;

        llhttp_errno_t err = llhttp_execute(&sess->parser, buf, n);
        if (err != HPE_OK && err != HPE_PAUSED) {
            logc::warn("llhttp: %s", llhttp_errno_name(err));
            session_close(sess);
            return;
        }
    }
}

// ---------------------------------------------------------------------------
// llhttp callbacks (store parsed data into the tls_session's HttpRequest)
// ---------------------------------------------------------------------------

static int cb_begin(llhttp_t* p) {
    auto* s = static_cast<tls_session*>(p->data);
    s->current_request = HttpRequest{};
    return 0;
}

static int cb_method(llhttp_t* p, const char* at, size_t len) {
    auto* s = static_cast<tls_session*>(p->data);
    s->current_request.method.assign(at, len);
    return 0;
}

static int cb_url(llhttp_t* p, const char* at, size_t len) {
    auto* s = static_cast<tls_session*>(p->data);
    std::string raw(at, len);
    size_t q = raw.find('?');
    if (q != std::string::npos) {
        s->current_request.query_string = raw.substr(q + 1);
        s->current_request.path = raw.substr(0, q);
    } else {
        s->current_request.path = raw;
    }
    return 0;
}

static int cb_header_field(llhttp_t* p, const char* at, size_t len) {
    auto* s = static_cast<tls_session*>(p->data);
    s->current_header_field.assign(at, len);
    return 0;
}

static int cb_header_value(llhttp_t* p, const char* at, size_t len) {
    auto* s = static_cast<tls_session*>(p->data);
    s->current_request.headers[s->current_header_field].assign(at, len);
    return 0;
}

static int cb_headers_complete(llhttp_t* p) {
    auto* s = static_cast<tls_session*>(p->data);
    auto it = s->current_request.headers.find("Content-Length");
    if (it != s->current_request.headers.end()) {
        s->body_length = static_cast<uint32_t>(std::stoul(it->second));
    }
    return 0;
}

static int cb_body(llhttp_t* p, const char* at, size_t len) {
    auto* s = static_cast<tls_session*>(p->data);
    auto& body = s->current_request.body;
    body.insert(body.end(),
                reinterpret_cast<const uint8_t*>(at),
                reinterpret_cast<const uint8_t*>(at + len));
    return 0;
}

static int cb_message_complete(llhttp_t* p) {
    auto* s = static_cast<tls_session*>(p->data);
    s->current_response = HttpResponse{};
    s->current_response.session = s;

    // Normalize /api/v1 -> /v1
    s->current_request.path = normalize_path(s->current_request.path);

    // Reset idle timer
    uv_timer_stop(&s->timer_handle);
    uv_timer_start(&s->timer_handle,
        [](uv_timer_t* h) {
            session_close(static_cast<tls_session*>(h->data));
        }, 300'000, 0);

    if (g_on_request) g_on_request(s);
    return 0;
}

static void init_parser(tls_session* s) {
    llhttp_settings_init(&s->parser_settings);
    s->parser_settings.on_message_begin    = cb_begin;
    s->parser_settings.on_method           = cb_method;
    s->parser_settings.on_url              = cb_url;
    s->parser_settings.on_header_field     = cb_header_field;
    s->parser_settings.on_header_value     = cb_header_value;
    s->parser_settings.on_headers_complete = cb_headers_complete;
    s->parser_settings.on_body             = cb_body;
    s->parser_settings.on_message_complete = cb_message_complete;

    llhttp_init(&s->parser, HTTP_REQUEST, &s->parser_settings);
    s->parser.data = s;
}

// ---------------------------------------------------------------------------
// Session lifecycle
// ---------------------------------------------------------------------------

static void on_alloc(uv_handle_t*, size_t suggested, uv_buf_t* buf) {
    buf->base = new char[suggested];
    buf->len  = suggested;
}

static void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
    auto* sess = static_cast<tls_session*>(stream->data);

    if (nread > 0 && sess->ssl) {
        // Feed encrypted bytes into the read BIO
        BIO_write(sess->rbio, buf->base, nread);

        // TLS handshake (may need multiple rounds)
        if (!sess->handshake_done) {
            int ret = SSL_do_handshake(sess->ssl);
            if (ret == 1) {
                sess->handshake_done = true;
                flush_encrypted(sess);
            } else {
                int err = SSL_get_error(sess->ssl, ret);
                if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
                    flush_encrypted(sess);
                    delete[] buf->base;
                    return;
                }
                ssl_log_errors("handshake");
                delete[] buf->base;
                session_close(sess);
                return;
            }
        }

        // Read plaintext and pump through llhttp
        drain_ssl_to_llhttp(sess);
    }

    if (nread < 0 && nread != UV_EOF) {
        logc::info("client disconnected: %s", uv_strerror(static_cast<int>(nread)));
    }
    if (nread < 0) {
        delete[] buf->base;
        session_close(sess);
        return;
    }

    delete[] buf->base;
}

static void on_close(uv_handle_t* handle) {
    auto* sess = static_cast<tls_session*>(handle->data);
    if (sess->ssl) { SSL_free(sess->ssl); sess->ssl = nullptr; }
    delete sess;
}

// ---------------------------------------------------------------------------
// New connection
// ---------------------------------------------------------------------------

static void on_connection(uv_stream_t* server, int status) {
    if (status < 0) {
        logc::error("accept: %s", uv_strerror(status));
        return;
    }

    auto* sess = new tls_session{};
    sess->tcp_handle.data = sess;

    uv_tcp_init(g_loop, &sess->tcp_handle);

    if (uv_accept(server, reinterpret_cast<uv_stream_t*>(&sess->tcp_handle)) != 0) {
        uv_close(reinterpret_cast<uv_handle_t*>(&sess->tcp_handle), on_close);
        return;
    }

    uv_tcp_nodelay(&sess->tcp_handle, 1);

    // OpenSSL
    sess->ssl   = SSL_new(g_ssl_ctx);
    sess->rbio  = BIO_new(BIO_s_mem());
    sess->wbio  = BIO_new(BIO_s_mem());
    SSL_set_bio(sess->ssl, sess->rbio, sess->wbio);
    SSL_set_accept_state(sess->ssl);

    // llhttp
    init_parser(sess);

    // Peer address
    struct sockaddr_storage saddr;
    int slen = sizeof(saddr);
    uv_tcp_getpeername(&sess->tcp_handle, reinterpret_cast<struct sockaddr*>(&saddr), &slen);
    char addr_buf[INET6_ADDRSTRLEN] = {};
    if (saddr.ss_family == AF_INET)
        uv_ip4_name(reinterpret_cast<struct sockaddr_in*>(&saddr), addr_buf, sizeof(addr_buf));
    else
        uv_ip6_name(reinterpret_cast<struct sockaddr_in6*>(&saddr), addr_buf, sizeof(addr_buf));
    sess->peer_ip = addr_buf;

    // Read encrypted data
    uv_read_start(reinterpret_cast<uv_stream_t*>(&sess->tcp_handle), on_alloc, on_read);

    // Idle timer
    uv_timer_init(g_loop, &sess->timer_handle);
    sess->timer_handle.data = sess;
    uv_timer_start(&sess->timer_handle,
        [](uv_timer_t* h) {
            logc::info("idle timeout");
            session_close(static_cast<tls_session*>(h->data));
        }, 300'000, 0);
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

void tls_server_set_handler(void (*handler)(tls_session*)) {
    g_on_request = handler;
}

int tls_server_init(uv_loop_t* loop, const char* host, int port,
                    const char* cert_path, const char* key_path) {
    g_loop = loop;

    g_ssl_ctx = SSL_CTX_new(TLS_server_method());
    if (!g_ssl_ctx) { ssl_log_errors("SSL_CTX_new"); return -1; }

    SSL_CTX_set_min_proto_version(g_ssl_ctx, TLS1_2_VERSION);

    if (SSL_CTX_use_certificate_chain_file(g_ssl_ctx, cert_path) != 1) {
        ssl_log_errors("cert"); return -1;
    }
    if (SSL_CTX_use_PrivateKey_file(g_ssl_ctx, key_path, SSL_FILETYPE_PEM) != 1) {
        ssl_log_errors("key"); return -1;
    }
    if (SSL_CTX_check_private_key(g_ssl_ctx) != 1) {
        ssl_log_errors("check_key"); return -1;
    }

    uv_tcp_init(loop, &g_server);
    g_server.data = nullptr;

    struct sockaddr_in addr;
    uv_ip4_addr(host, port, &addr);

    int r = uv_tcp_bind(&g_server, reinterpret_cast<struct sockaddr*>(&addr), 0);
    if (r) { logc::error("bind: %s", uv_strerror(r)); return r; }

    r = uv_listen(reinterpret_cast<uv_stream_t*>(&g_server), 128, on_connection);
    if (r) { logc::error("listen: %s", uv_strerror(r)); return r; }

    logc::success("HTTPS server listening on %s:%d", host, port);
    return 0;
}

static void tls_server_shutdown() {
    logc::info("shutting down...");
    uv_signal_stop(&g_sigint);
    uv_signal_stop(&g_sigterm);
    uv_close(reinterpret_cast<uv_handle_t*>(&g_sigint), nullptr);
    uv_close(reinterpret_cast<uv_handle_t*>(&g_sigterm), nullptr);
    uv_close(reinterpret_cast<uv_handle_t*>(&g_server), nullptr);
    uv_stop(g_loop);
}

void tls_server_install_signals(uv_loop_t* loop) {
    uv_signal_init(loop, &g_sigint);
    uv_signal_start(&g_sigint, [](uv_signal_t*, int) { tls_server_shutdown(); }, SIGINT);
    uv_signal_init(loop, &g_sigterm);
    uv_signal_start(&g_sigterm, [](uv_signal_t*, int) { tls_server_shutdown(); }, SIGTERM);
}

void session_write(tls_session* sess, const std::string& data) {
    if (!sess->ssl || sess->tcp_handle.type != UV_TCP) return;
    SSL_write(sess->ssl, data.data(), data.size());
    flush_encrypted(sess);
}

void session_write_binary(tls_session* sess, const std::string& header,
                          const std::vector<uint8_t>& data) {
    if (!sess->ssl || sess->tcp_handle.type != UV_TCP) return;
    SSL_write(sess->ssl, header.data(), header.size());
    if (!data.empty())
        SSL_write(sess->ssl, data.data(), data.size());
    flush_encrypted(sess);
}

void session_close(tls_session* sess) {
    if (sess->tcp_handle.type != UV_TCP) return;
    uv_timer_stop(&sess->timer_handle);
    uv_close(reinterpret_cast<uv_handle_t*>(&sess->timer_handle), nullptr);
    uv_close(reinterpret_cast<uv_handle_t*>(&sess->tcp_handle), on_close);
}

const char* reason_phrase(int code) {
    switch (code) {
        case 200: return "OK";
        case 400: return "Bad Request";
        case 404: return "Not Found";
        case 500: return "Internal Server Error";
        default:  return "Unknown";
    }
}
