/*
 * http_listener.c — minimal HTTP/1.1 parser for n_signer's HTTP listener mode.
 *
 * Only supports POST with a JSON body. No chunked encoding, no keep-alive,
 * no static file serving. One request per connection (connection close after
 * response).
 *
 * This is intentionally minimal — n_signer is a signer, not a web server.
 */
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>

/* ------------------------------------------------------------------ */
/* Read a line from fd into buf (up to buf_size-1 chars, NUL-terminated).
 * Returns line length (excluding NUL) on success, -1 on error/EOF.
 * The line includes the trailing \r\n if present. */
static int read_line_fd(int fd, char *buf, size_t buf_size) {
    size_t pos = 0;
    while (pos < buf_size - 1) {
        char c;
        ssize_t n = read(fd, &c, 1);
        if (n <= 0) {
            if (pos == 0) return -1;
            break;
        }
        buf[pos++] = c;
        if (c == '\n') break;
    }
    buf[pos] = '\0';
    return (int)pos;
}

/* Read exactly n bytes from fd into buf. Returns 0 on success, -1 on error. */
static int read_n_bytes(int fd, char *buf, size_t n) {
    size_t got = 0;
    while (got < n) {
        ssize_t r = read(fd, buf + got, n - got);
        if (r <= 0) return -1;
        got += (size_t)r;
    }
    return 0;
}

/* Write all bytes to fd. Returns 0 on success, -1 on error. */
static int write_all(int fd, const char *buf, size_t len) {
    size_t put = 0;
    while (put < len) {
        ssize_t w = write(fd, buf + put, len - put);
        if (w <= 0) {
            if (errno == EINTR) continue;
            return -1;
        }
        put += (size_t)w;
    }
    return 0;
}

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

/*
 * Read an HTTP POST request from fd and return the JSON body.
 *
 * On success returns 0 and sets *out_body to a malloc'd NUL-terminated
 * string (caller frees). Returns -1 on error.
 */
int http_recv_request(int fd, char **out_body, size_t max_body_size) {
    char line[2048];
    long content_length = -1;
    int is_post = 0;

    if (!out_body) return -1;
    *out_body = NULL;

    /* Read request line: "METHOD PATH HTTP/1.1\r\n" */
    if (read_line_fd(fd, line, sizeof(line)) < 0) {
        return -1;
    }
    /* Parse method. */
    if (strncmp(line, "POST", 4) == 0) {
        is_post = 1;
    } else if (strncmp(line, "OPTIONS", 7) == 0) {
        /* CORS preflight — read remaining headers, then return a special
         * marker so the caller can send CORS headers. */
        while (read_line_fd(fd, line, sizeof(line)) > 0) {
            if (strcmp(line, "\r\n") == 0 || strcmp(line, "\n") == 0) break;
        }
        *out_body = strdup("");  /* empty body signals OPTIONS */
        return 0;
    }

    if (!is_post) {
        /* Not POST — read remaining headers so we can send a 405. */
        while (read_line_fd(fd, line, sizeof(line)) > 0) {
            if (strcmp(line, "\r\n") == 0 || strcmp(line, "\n") == 0) break;
        }
        return -2;  /* method not allowed */
    }

    /* Read headers until empty line. */
    while (read_line_fd(fd, line, sizeof(line)) > 0) {
        /* Strip trailing \r\n. */
        size_t len = strlen(line);
        while (len > 0 && (line[len-1] == '\r' || line[len-1] == '\n')) {
            line[--len] = '\0';
        }
        if (len == 0) break;  /* end of headers */

        /* Parse Content-Length (case-insensitive). */
        if (strncasecmp(line, "Content-Length:", 15) == 0) {
            const char *p = line + 15;
            while (*p == ' ' || *p == '\t') p++;
            content_length = atol(p);
        }
    }

    if (content_length < 0) {
        return -3;  /* missing Content-Length */
    }
    if ((size_t)content_length > max_body_size) {
        /* Drain the body so the connection isn't left half-open. */
        char tmp[4096];
        long remaining = content_length;
        while (remaining > 0) {
            size_t to_read = (size_t)remaining;
            if (to_read > sizeof(tmp)) to_read = sizeof(tmp);
            ssize_t r = read(fd, tmp, to_read);
            if (r <= 0) break;
            remaining -= r;
        }
        return -4;  /* body too large */
    }

    /* Read the body. */
    char *body = (char *)malloc((size_t)content_length + 1);
    if (!body) return -1;
    if (read_n_bytes(fd, body, (size_t)content_length) != 0) {
        free(body);
        return -1;
    }
    body[content_length] = '\0';
    *out_body = body;
    return 0;
}

/*
 * Send an HTTP response with a JSON body.
 */
int http_send_response(int fd, const char *json_body) {
    if (!json_body) json_body = "";
    size_t body_len = strlen(json_body);

    char header[512];
    int hlen = snprintf(header, sizeof(header),
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: application/json\r\n"
        "Content-Length: %zu\r\n"
        "Access-Control-Allow-Origin: *\r\n"
        "Access-Control-Allow-Methods: POST, OPTIONS\r\n"
        "Access-Control-Allow-Headers: Content-Type\r\n"
        "Connection: close\r\n"
        "\r\n",
        body_len);

    if (write_all(fd, header, (size_t)hlen) != 0) return -1;
    if (write_all(fd, json_body, body_len) != 0) return -1;
    return 0;
}

/*
 * Send an HTTP error response.
 */
int http_send_error(int fd, int status, const char *message) {
    if (!message) message = "error";
    char body[512];
    int blen = snprintf(body, sizeof(body),
        "{\"error\":{\"code\":%d,\"message\":\"%s\"}}", status, message);

    char header[512];
    int hlen = snprintf(header, sizeof(header),
        "HTTP/1.1 %d %s\r\n"
        "Content-Type: application/json\r\n"
        "Content-Length: %d\r\n"
        "Access-Control-Allow-Origin: *\r\n"
        "Connection: close\r\n"
        "\r\n",
        status,
        (status == 405) ? "Method Not Allowed" :
        (status == 413) ? "Payload Too Large" :
        (status == 400) ? "Bad Request" : "Internal Server Error",
        blen);

    if (write_all(fd, header, (size_t)hlen) != 0) return -1;
    if (write_all(fd, body, (size_t)blen) != 0) return -1;
    return 0;
}

/*
 * Send a CORS preflight response (for OPTIONS requests).
 */
int http_send_cors_preflight(int fd) {
    const char *resp =
        "HTTP/1.1 204 No Content\r\n"
        "Access-Control-Allow-Origin: *\r\n"
        "Access-Control-Allow-Methods: POST, OPTIONS\r\n"
        "Access-Control-Allow-Headers: Content-Type\r\n"
        "Access-Control-Max-Age: 86400\r\n"
        "Content-Length: 0\r\n"
        "Connection: close\r\n"
        "\r\n";
    return write_all(fd, resp, strlen(resp));
}
