/*
 * http.hpp
 * Simple HTTP request builder and response parser for MontaukOS
 * Wraps tls::https_fetch() and raw sockets for ergonomic HTTP usage.
 */

#pragma once

#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/heap.h>
#include <tls/tls.hpp>

namespace http {

// ----------------------------------------------------------------------------
// Response
// ----------------------------------------------------------------------------

struct Response {
    int status;           // HTTP status code (200, 404, etc.) or -1 on error
    const char* headers;  // Pointer into raw buffer (header block)
    int headers_len;
    const char* body;     // Pointer into raw buffer (body)
    int body_len;
    char* raw;            // Owned buffer — caller must free with montauk::mfree()
    int raw_len;
};

inline const char* find_header_block_end(const char* start, const char* end) {
    if (!start || !end || start >= end) return nullptr;

    for (const char* s = start; s < end - 3; s++) {
        if (s[0] == '\r' && s[1] == '\n' && s[2] == '\r' && s[3] == '\n')
            return s + 4;
    }
    for (const char* s = start; s < end - 1; s++) {
        if (s[0] == '\n' && s[1] == '\n')
            return s + 2;
    }
    return nullptr;
}

inline int parse_status_code(const char* start, const char* end) {
    if (!start || !end || end - start < 12) return -1;

    const char* p = start;
    while (p < end && *p && *p != ' ') p++;
    if (p >= end || *p != ' ') return -1;
    p++;

    int code = 0;
    int digits = 0;
    while (digits < 3 && p < end && *p >= '0' && *p <= '9') {
        code = code * 10 + (*p - '0');
        p++;
        digits++;
    }
    return digits == 3 ? code : -1;
}

inline const char* find_final_response_start(const char* buf, int len) {
    if (!buf || len <= 0) return nullptr;

    const char* start = buf;
    const char* end = buf + len;
    for (;;) {
        int code = parse_status_code(start, end);
        if (code < 0) return nullptr;
        if (code < 100 || code >= 200) return start;

        const char* next = find_header_block_end(start, end);
        if (!next || next >= end) return nullptr;
        if (end - next < 5) return nullptr;
        if (!(next[0] == 'H' && next[1] == 'T' && next[2] == 'T' && next[3] == 'P' && next[4] == '/'))
            return nullptr;
        start = next;
    }
}

inline const char* skip_informational_responses(const char* buf, int len) {
    const char* start = find_final_response_start(buf, len);
    return start ? start : buf;
}

// Parse raw HTTP response in-place. Sets pointers into buf (does not copy).
// Skips leading informational 1xx responses such as "100 Continue".
// Returns status code, or -1 if unparseable.
inline int parse_response(char* buf, int len, Response* out) {
    out->raw = buf;
    out->raw_len = len;
    out->status = -1;
    out->headers = nullptr;
    out->headers_len = 0;
    out->body = nullptr;
    out->body_len = 0;

    const char* start = find_final_response_start(buf, len);
    const char* end = buf + len;
    if (!start || end - start < 12) return -1;  // "HTTP/1.x NNN"

    // Parse status code from "HTTP/1.x NNN"
    int code = parse_status_code(start, end);
    if (code < 0) return -1;
    out->status = code;

    // Headers start after the status line
    const char* hdr_start = start;
    while (hdr_start < end - 1) {
        if (*hdr_start == '\r' && *(hdr_start + 1) == '\n') { hdr_start += 2; break; }
        if (*hdr_start == '\n') { hdr_start++; break; }
        hdr_start++;
    }
    out->headers = hdr_start;

    // Find \r\n\r\n boundary between headers and body
    const char* body = find_header_block_end(hdr_start, end);
    if (body) {
        if (body >= hdr_start + 4 &&
            body[-4] == '\r' && body[-3] == '\n' && body[-2] == '\r' && body[-1] == '\n')
            out->headers_len = (int)((body - 4) - hdr_start);
        else if (body >= hdr_start + 2 &&
                 body[-2] == '\n' && body[-1] == '\n')
            out->headers_len = (int)((body - 2) - hdr_start);
        else
            out->headers_len = (int)(body - hdr_start);
        out->body = body;
        out->body_len = len - (int)(out->body - buf);
        return code;
    }

    // No body separator found — entire remainder is headers
    out->headers_len = len - (int)(hdr_start - buf);
    return code;
}

// Find a header value by name (case-insensitive match on the name).
// Writes value into out_val (up to max_len), returns true if found.
inline bool get_header(const Response* resp, const char* name, char* out_val, int max_len) {
    if (!resp->headers || resp->headers_len == 0) return false;
    int name_len = montauk::slen(name);
    const char* p = resp->headers;
    const char* end = resp->headers + resp->headers_len;

    while (p < end) {
        // Case-insensitive prefix match
        bool match = true;
        if (p + name_len >= end) { match = false; }
        else {
            for (int i = 0; i < name_len; i++) {
                char a = p[i], b = name[i];
                if (a >= 'A' && a <= 'Z') a += 32;
                if (b >= 'A' && b <= 'Z') b += 32;
                if (a != b) { match = false; break; }
            }
            if (match && p[name_len] != ':') match = false;
        }

        if (match) {
            const char* v = p + name_len + 1;
            while (v < end && *v == ' ') v++;  // skip OWS
            int i = 0;
            while (v < end && *v != '\r' && *v != '\n' && i < max_len - 1)
                out_val[i++] = *v++;
            out_val[i] = 0;
            return true;
        }

        // Skip to next line
        while (p < end && *p != '\n') p++;
        if (p < end) p++;
    }
    return false;
}

// Free a response's raw buffer.
inline void free_response(Response* resp) {
    if (resp->raw) { montauk::mfree(resp->raw); resp->raw = nullptr; }
}

// ----------------------------------------------------------------------------
// Request builder (internal)
// ----------------------------------------------------------------------------

inline int build_request(char* buf, int buf_size,
                         const char* method, const char* host,
                         const char* path, const char* content_type,
                         const char* body_data, int body_len,
                         const char* extra_headers) {
    char* p = buf;
    char* end = buf + buf_size - 1;

    auto append = [&](const char* s) {
        while (*s && p < end) *p++ = *s++;
    };
    auto append_int = [&](int n) {
        char tmp[16]; int ti = 0;
        if (n == 0) { if (p < end) *p++ = '0'; return; }
        while (n > 0) { tmp[ti++] = '0' + (n % 10); n /= 10; }
        for (int j = ti - 1; j >= 0 && p < end; j--) *p++ = tmp[j];
    };

    // Request line
    append(method); append(" "); append(path); append(" HTTP/1.1\r\n");

    // Host
    append("Host: "); append(host); append("\r\n");

    // Content headers (for POST/PUT/PATCH)
    if (body_data && body_len > 0) {
        if (content_type) {
            append("Content-Type: "); append(content_type); append("\r\n");
        }
        append("Content-Length: "); append_int(body_len); append("\r\n");
    }

    // Extra headers (caller-supplied, must include \r\n terminators)
    if (extra_headers) append(extra_headers);

    append("Connection: close\r\n");
    append("\r\n");

    int header_len = (int)(p - buf);

    // Append body
    if (body_data && body_len > 0 && header_len + body_len < buf_size) {
        montauk::memcpy(p, body_data, body_len);
        p += body_len;
    }

    return (int)(p - buf);
}

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

// GET request over HTTPS. Returns parsed response. Caller must free_response().
inline Response get(const char* host, const char* path,
                    const tls::TrustAnchors& tas,
                    int resp_buf_size = 32768,
                    const char* extra_headers = nullptr,
                    tls::AbortCheckFn abort_check = nullptr) {
    Response resp = {};
    resp.status = -1;

    uint32_t ip = montauk::resolve(host);
    if (!ip) return resp;

    char req[1024];
    int reqLen = build_request(req, sizeof(req), "GET", host, path,
                               nullptr, nullptr, 0, extra_headers);

    char* buf = (char*)montauk::malloc(resp_buf_size);
    if (!buf) return resp;

    int n = tls::https_fetch(host, ip, 443, req, reqLen, tas,
                             buf, resp_buf_size - 1, abort_check);
    if (n <= 0) { montauk::mfree(buf); return resp; }
    buf[n] = 0;

    parse_response(buf, n, &resp);
    return resp;
}

// POST request over HTTPS. Returns parsed response. Caller must free_response().
inline Response post(const char* host, const char* path,
                     const char* content_type,
                     const char* body_data, int body_len,
                     const tls::TrustAnchors& tas,
                     int resp_buf_size = 32768,
                     const char* extra_headers = nullptr,
                     tls::AbortCheckFn abort_check = nullptr) {
    Response resp = {};
    resp.status = -1;

    uint32_t ip = montauk::resolve(host);
    if (!ip) return resp;

    int req_size = 1024 + body_len;
    char* req = (char*)montauk::malloc(req_size);
    if (!req) return resp;

    int reqLen = build_request(req, req_size, "POST", host, path,
                               content_type, body_data, body_len,
                               extra_headers);

    char* buf = (char*)montauk::malloc(resp_buf_size);
    if (!buf) { montauk::mfree(req); return resp; }

    int n = tls::https_fetch(host, ip, 443, req, reqLen, tas,
                             buf, resp_buf_size - 1, abort_check);
    montauk::mfree(req);

    if (n <= 0) { montauk::mfree(buf); return resp; }
    buf[n] = 0;

    parse_response(buf, n, &resp);
    return resp;
}

// Generic request over HTTPS (PUT, PATCH, DELETE, etc.).
inline Response request(const char* method,
                        const char* host, const char* path,
                        const char* content_type,
                        const char* body_data, int body_len,
                        const tls::TrustAnchors& tas,
                        int resp_buf_size = 32768,
                        const char* extra_headers = nullptr,
                        tls::AbortCheckFn abort_check = nullptr) {
    Response resp = {};
    resp.status = -1;

    uint32_t ip = montauk::resolve(host);
    if (!ip) return resp;

    int req_size = 1024 + (body_len > 0 ? body_len : 0);
    char* req = (char*)montauk::malloc(req_size);
    if (!req) return resp;

    int reqLen = build_request(req, req_size, method, host, path,
                               content_type, body_data, body_len,
                               extra_headers);

    char* buf = (char*)montauk::malloc(resp_buf_size);
    if (!buf) { montauk::mfree(req); return resp; }

    int n = tls::https_fetch(host, ip, 443, req, reqLen, tas,
                             buf, resp_buf_size - 1, abort_check);
    montauk::mfree(req);

    if (n <= 0) { montauk::mfree(buf); return resp; }
    buf[n] = 0;

    parse_response(buf, n, &resp);
    return resp;
}

// Plain HTTP (no TLS) GET over port 80.
inline Response get_plain(const char* host, const char* path,
                          int resp_buf_size = 32768,
                          const char* extra_headers = nullptr) {
    Response resp = {};
    resp.status = -1;

    uint32_t ip = montauk::resolve(host);
    if (!ip) return resp;

    char req[1024];
    int reqLen = build_request(req, sizeof(req), "GET", host, path,
                               nullptr, nullptr, 0, extra_headers);

    int sock = montauk::socket(Montauk::SOCK_TCP);
    if (sock < 0) return resp;
    if (montauk::connect(sock, ip, 80) < 0) { montauk::closesocket(sock); return resp; }

    montauk::send(sock, req, reqLen);

    char* buf = (char*)montauk::malloc(resp_buf_size);
    if (!buf) { montauk::closesocket(sock); return resp; }

    int total = 0;
    while (total < resp_buf_size - 1) {
        int n = montauk::recv(sock, buf + total, resp_buf_size - 1 - total);
        if (n <= 0) break;
        total += n;
    }
    montauk::closesocket(sock);

    if (total <= 0) { montauk::mfree(buf); return resp; }
    buf[total] = 0;

    parse_response(buf, total, &resp);
    return resp;
}

} // namespace http
