// http_playdate.c - HTTP module for Playdate using Playdate Network API
// Note: Playdate HTTP does not support SSL/HTTPS

#include "cell.h"
#include "pd_api.h"
#include <string.h>
#include <stdlib.h>

// Global Playdate API pointers - defined in main_playdate.c
extern const struct playdate_network *pd_network;
extern const struct playdate_sys *pd_sys;

#if TARGET_EXTENSION

// Context for async HTTP fetch
typedef struct {
    JSContext *js;
    HTTPConnection *conn;
    uint8_t *data;
    size_t data_len;
    size_t data_cap;
    int complete;
    int success;
    int status_code;
} http_fetch_ctx;

static void http_response_callback(HTTPConnection *conn) {
    http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
    if (!ctx) return;
    
    ctx->status_code = pd_network->http->getResponseStatus(conn);
    
    // Read all available data
    while (1) {
        size_t avail = pd_network->http->getBytesAvailable(conn);
        if (avail == 0) break;
        
        // Grow buffer if needed
        if (ctx->data_len + avail > ctx->data_cap) {
            size_t new_cap = ctx->data_cap * 2;
            if (new_cap < ctx->data_len + avail) new_cap = ctx->data_len + avail + 4096;
            uint8_t *new_data = realloc(ctx->data, new_cap);
            if (!new_data) {
                ctx->success = 0;
                ctx->complete = 1;
                return;
            }
            ctx->data = new_data;
            ctx->data_cap = new_cap;
        }
        
        int read = pd_network->http->read(conn, ctx->data + ctx->data_len, (unsigned int)avail);
        if (read < 0) {
            ctx->success = 0;
            ctx->complete = 1;
            return;
        }
        ctx->data_len += read;
    }
}

static void http_complete_callback(HTTPConnection *conn) {
    http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
    if (!ctx) return;
    
    // Read any remaining data
    http_response_callback(conn);
    
    ctx->success = (ctx->status_code >= 200 && ctx->status_code < 400);
    ctx->complete = 1;
}

static void http_closed_callback(HTTPConnection *conn) {
    http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
    if (!ctx) return;
    ctx->complete = 1;
}

// Parse URL into host, port, path, and check if HTTPS
static int parse_url(const char *url, char **host, int *port, char **path, int *is_https) {
    *host = NULL;
    *path = NULL;
    *port = 80;
    *is_https = 0;
    
    const char *p = url;
    
    // Check scheme
    if (strncmp(p, "https://", 8) == 0) {
        *is_https = 1;
        *port = 443;
        p += 8;
    } else if (strncmp(p, "http://", 7) == 0) {
        p += 7;
    } else {
        return -1; // Invalid scheme
    }
    
    // Find end of host (either :, /, or end of string)
    const char *host_start = p;
    const char *host_end = p;
    while (*host_end && *host_end != ':' && *host_end != '/') host_end++;
    
    size_t host_len = host_end - host_start;
    *host = malloc(host_len + 1);
    if (!*host) return -1;
    memcpy(*host, host_start, host_len);
    (*host)[host_len] = '\0';
    
    p = host_end;
    
    // Check for port
    if (*p == ':') {
        p++;
        *port = atoi(p);
        while (*p && *p != '/') p++;
    }
    
    // Get path (default to "/" if none)
    if (*p == '/') {
        *path = strdup(p);
    } else {
        *path = strdup("/");
    }
    
    if (!*path) {
        free(*host);
        *host = NULL;
        return -1;
    }
    
    return 0;
}

// Performs a blocking HTTP GET and returns a QuickJS Blob of the body
static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val,
                                  int argc, JSValueConst *argv) {
    if (argc < 1 || !JS_IsText(argv[0]))
        return JS_ThrowTypeError(ctx, "fetch: URL string required");
    
    if (!pd_network || !pd_network->http) {
        return JS_ThrowInternalError(ctx, "fetch: Playdate network API not available");
    }
    
    const char *url = JS_ToCString(ctx, argv[0]);
    if (!url) return JS_ThrowTypeError(ctx, "fetch: invalid URL");
    
    char *host = NULL;
    char *path = NULL;
    int port = 80;
    int is_https = 0;
    
    if (parse_url(url, &host, &port, &path, &is_https) < 0) {
        JS_FreeCString(ctx, url);
        return JS_ThrowTypeError(ctx, "fetch: failed to parse URL");
    }
    
    JS_FreeCString(ctx, url);
    
    // Playdate doesn't support HTTPS
    if (is_https) {
        free(host);
        free(path);
        return JS_ThrowTypeError(ctx, "fetch: HTTPS not supported on Playdate");
    }
    
    // Create HTTP connection
    HTTPConnection *conn = pd_network->http->newConnection(host, port, 0);
    free(host);
    
    if (!conn) {
        free(path);
        return JS_ThrowInternalError(ctx, "fetch: failed to create connection");
    }
    
    // Set up context
    http_fetch_ctx fetch_ctx = {0};
    fetch_ctx.js = ctx;
    fetch_ctx.conn = conn;
    fetch_ctx.data = malloc(4096);
    fetch_ctx.data_cap = 4096;
    fetch_ctx.data_len = 0;
    fetch_ctx.complete = 0;
    fetch_ctx.success = 0;
    
    if (!fetch_ctx.data) {
        pd_network->http->release(conn);
        free(path);
        return JS_ThrowInternalError(ctx, "fetch: malloc failed");
    }
    
    pd_network->http->setUserdata(conn, &fetch_ctx);
    pd_network->http->setResponseCallback(conn, http_response_callback);
    pd_network->http->setRequestCompleteCallback(conn, http_complete_callback);
    pd_network->http->setConnectionClosedCallback(conn, http_closed_callback);
    pd_network->http->setConnectTimeout(conn, 30000); // 30 second timeout
    pd_network->http->setReadTimeout(conn, 30000);
    
    // Start the GET request
    PDNetErr err = pd_network->http->get(conn, path, NULL, 0);
    free(path);
    
    if (err != NET_OK) {
        free(fetch_ctx.data);
        pd_network->http->release(conn);
        return JS_ThrowInternalError(ctx, "fetch: request failed with error %d", err);
    }
    
    // Poll until complete (blocking)
    // Note: This is a simple blocking implementation. In a real game,
    // you'd want to use async callbacks instead.
    while (!fetch_ctx.complete) {
        // Small delay to avoid busy-waiting
        pd_sys->delay(10);
    }
    
    pd_network->http->close(conn);
    pd_network->http->release(conn);
    
    if (!fetch_ctx.success) {
        free(fetch_ctx.data);
        return JS_ThrowTypeError(ctx, "fetch: request failed (status %d)", fetch_ctx.status_code);
    }
    
    // Return a Blob wrapping the data
    JSValue blob = js_new_blob_stoned_copy(ctx, fetch_ctx.data, fetch_ctx.data_len);
    free(fetch_ctx.data);
    return blob;
}

#else
// Simulator/non-extension build - provide stub
static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val,
                                  int argc, JSValueConst *argv) {
    return JS_ThrowInternalError(ctx, "fetch: not available in simulator");
}
#endif

static const JSCFunctionListEntry js_http_funcs[] = {
    JS_CFUNC_DEF("fetch", 2, js_fetch_playdate),
};

JSValue js_http_use(JSContext *js) {
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyFunctionList(js, obj, js_http_funcs,
                               sizeof(js_http_funcs)/sizeof(js_http_funcs[0]));
    return obj;
}
