#include "qjs_blob.h"
#include "picohttpparser.h"
#include <mbedtls/net_sockets.h>
#include <mbedtls/ssl.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/error.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#if defined(__MINGW32__) || defined(__MINGW64__)
static void *memmem(const void *hay, size_t haylen,
             const void *ndl, size_t ndllen) {
  const unsigned char *h = hay, *n = ndl;
  if (!ndllen) return (void*)h;
  haylen -= ndllen - 1;
  for (size_t i = 0; i < haylen; i++)
    if (memcmp(h + i, n, ndllen) == 0)
      return (void*)(h + i);
  return NULL;
}

static char *strndup(const char *s, size_t n) {
  size_t len = strnlen(s, n);
  char *d = malloc(len + 1);
  if (!d) return NULL;
  memcpy(d, s, len);
  d[len] = '\0';
  return d;
}
#endif

// Simple dynamic buffer for reading the response
typedef struct {
  char   *data;
  size_t  size;
  size_t  capacity;
} buffer_t;

static void buffer_init(buffer_t *b) {
  b->data     = NULL;
  b->size     = 0;
  b->capacity = 0;
}
static int buffer_append(buffer_t *b, const void *p, size_t len) {
  if (b->size + len > b->capacity) {
    size_t new_cap = b->capacity ? b->capacity * 2 : 4096;
    while (new_cap < b->size + len) new_cap *= 2;
    char *nx = realloc(b->data, new_cap);
    if (!nx) return -1;
    b->data     = nx;
    b->capacity = new_cap;
  }
  memcpy(b->data + b->size, p, len);
  b->size += len;
  return 0;
}
static void buffer_free(buffer_t *b) {
  free(b->data);
  b->data     = NULL;
  b->size     = 0;
  b->capacity = 0;
}

// Parses "http://host:port/path" or "https://host:port/path"
static int parse_url(const char *url, int *use_ssl,
                     char **host, char **port, char **path) {
  *host = *port = *path = NULL;
  *use_ssl = 0;
  const char *p = url;
  if (strncmp(p, "https://", 8) == 0) {
    *use_ssl = 1;
    p += 8;
  } else if (strncmp(p, "http://", 7) == 0) {
    *use_ssl = 0;
    p += 7;
  } else {
    return -1;
  }
  // find path
  const char *path_start = strchr(p, '/');
  size_t hostport_len = path_start ? (size_t)(path_start - p) : strlen(p);
  *path = path_start ? strdup(path_start) : strdup("/");
  if (!*path) return -1;

  // check for explicit port
  const char *colon = memmem(p, hostport_len, ":", 1);
  if (colon) {
    size_t hlen = (size_t)(colon - p);
    *host = strndup(p, hlen);
    size_t plen = hostport_len - hlen - 1;
    *port = strndup(colon + 1, plen);
  } else {
    *host = strndup(p, hostport_len);
    *port = strdup(*use_ssl ? "443" : "80");
  }
  if (!*host || !*port) {
    free(*host); free(*port); free(*path);
    return -1;
  }
  return 0;
}

// Builds a GET request into buf
static int build_get_request(buffer_t *buf,
                             const char *host, const char *port, const char *path) {
  // If port is “80” for HTTP or “443” for HTTPS, omit the “:port” in Host:
  char host_header[512];
  if ((strcmp(port, "80") == 0) || (strcmp(port, "443") == 0)) {
    // default port: just “Host: hostname”
    snprintf(host_header, sizeof(host_header), "Host: %s\r\n", host);
  } else {
    // non-default port: “Host: hostname:port”
    snprintf(host_header, sizeof(host_header), "Host: %s:%s\r\n", host, port);
  }

  // Now build the rest of the request, including a User-Agent:
  char tmp[1024];
  int n = snprintf(tmp, sizeof(tmp),
    "GET %s HTTP/1.1\r\n"
    "%s"                      // the host_header line
    "User-Agent: ProsperonFetch/1.0\r\n"
    "Accept: */*\r\n"
    "Connection: close\r\n"
    "\r\n",
    path, host_header);
  if (n < 0) return -1;
  return buffer_append(buf, tmp, (size_t)n);
}

// Performs a blocking HTTP GET and returns a QuickJS Blob of the body
static JSValue js_fetch_picoparser(JSContext *ctx, JSValueConst this_val,
                                   int argc, JSValueConst *argv) {
  if (argc < 1 || !JS_IsString(argv[0]))
    return JS_ThrowTypeError(ctx, "fetch: URL string required");

  const char *url = JS_ToCString(ctx, argv[0]);
  if (!url) return JS_ThrowTypeError(ctx, "fetch: invalid URL");

  int use_ssl = 0;
  char *host = NULL, *port = NULL, *path = NULL;
  if (parse_url(url, &use_ssl, &host, &port, &path) < 0) {
    JS_FreeCString(ctx, url);
    return JS_ThrowTypeError(ctx, "fetch: URL parse error");
  }

  // Prepare mbedTLS structures
  mbedtls_net_context   net_ctx;
  mbedtls_ssl_context   ssl;
  mbedtls_ssl_config    conf;
  mbedtls_ctr_drbg_context drbg;
  mbedtls_entropy_context  entropy;
  mbedtls_net_init(&net_ctx);
  mbedtls_ssl_init(&ssl);
  mbedtls_ssl_config_init(&conf);
  mbedtls_ctr_drbg_init(&drbg);
  mbedtls_entropy_init(&entropy);

  int ret;
  const char *pers = "js_fetch";
  if (use_ssl) {
    if ((ret = mbedtls_ctr_drbg_seed(&drbg, mbedtls_entropy_func, &entropy,
                                     (const unsigned char *)pers,
                                     strlen(pers))) != 0)
      goto cleanup_tls;
    if ((ret = mbedtls_ssl_config_defaults(&conf,
            MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM,
            MBEDTLS_SSL_PRESET_DEFAULT)) != 0)
      goto cleanup_tls;
    mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
    mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &drbg);
    if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0)
      goto cleanup_tls;
    if ((ret = mbedtls_ssl_set_hostname(&ssl, host)) != 0)
      goto cleanup_tls;
  }

  // Connect to host:port
  if ((ret = mbedtls_net_connect(&net_ctx, host, port,
                                 MBEDTLS_NET_PROTO_TCP)) != 0)
    goto cleanup_tls;

  if (use_ssl) {
    mbedtls_ssl_set_bio(&ssl, &net_ctx,
                       mbedtls_net_send, mbedtls_net_recv, NULL);
    while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) {
      if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
          ret != MBEDTLS_ERR_SSL_WANT_WRITE)
        goto cleanup_all;
    }
  }

  // Build and send GET request
  buffer_t req = {0}, resp = {0};
  buffer_init(&req);
  buffer_init(&resp);
  if (build_get_request(&req, host, port, path) < 0)
    goto cleanup_all;

  // Write request
  size_t off = 0;
  while (off < req.size) {
    if (use_ssl) {
      ret = mbedtls_ssl_write(&ssl,
                              (const unsigned char *)req.data + off,
                              req.size - off);
      if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
          ret == MBEDTLS_ERR_SSL_WANT_WRITE)
        continue;
    } else {
      ret = mbedtls_net_send(&net_ctx,
                             (unsigned char *)req.data + off,
                             req.size - off);
    }
    if (ret <= 0) goto cleanup_all;
    off += (size_t)ret;
  }
  buffer_free(&req);

  // Read entire response into resp.data
  unsigned char buf[4096];
  do {
    if (use_ssl) {
      ret = mbedtls_ssl_read(&ssl, buf, sizeof(buf));
      if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
          ret == MBEDTLS_ERR_SSL_WANT_WRITE)
        continue;
    } else {
      ret = mbedtls_net_recv(&net_ctx, buf, sizeof(buf));
    }
    if (ret > 0) {
      if (buffer_append(&resp, buf, (size_t)ret) < 0)
        goto cleanup_all;
    }
  } while (ret > 0);

  // Parse status-line + headers with picohttpparser
  struct phr_header hdrv[32];
  size_t hdr_count = sizeof(hdrv)/sizeof(hdrv[0]);
  int    minor, status;
  const char *reason = NULL;
  size_t     reason_len = 0;
  int    pret = phr_parse_response(
                   resp.data, resp.size,
                   &minor, &status,
                   &reason, &reason_len, /* provide pointers, even if unused */
                   hdrv, &hdr_count,
                   0);
  if (pret < 0) goto cleanup_all;
  size_t header_len = (size_t)pret;
  char *body_ptr = resp.data + header_len;
  size_t body_len  = resp.size - header_len;

  // Check for chunked encoding
  int is_chunked = 0;
  for (size_t i = 0; i < hdr_count; i++) {
    if ((hdrv[i].name_len == 17 &&
         !strncasecmp(hdrv[i].name, "Transfer-Encoding", 17) &&
         memmem(hdrv[i].value, hdrv[i].value_len, "chunked", 7))) {
      is_chunked = 1;
      break;
    }
  }

  // If chunked, decode in-place
  if (is_chunked) {
    struct phr_chunked_decoder cd = {0};
    size_t blen = body_len;
    ssize_t rest = phr_decode_chunked(&cd, body_ptr, &blen);
    if (rest < 0) goto cleanup_all;
    // blen = decoded length; rest = trailer start offset (ignored)
    body_len = blen;
  }

  // Return a Blob wrapping the body buffer (no extra copy)
  JSValue blob = js_new_blob_stoned_copy(ctx,
                        (uint8_t *)body_ptr, body_len);

cleanup_all:
  buffer_free(&resp);
  mbedtls_ssl_close_notify(&ssl);

cleanup_tls:
  mbedtls_net_free(&net_ctx);
  mbedtls_ssl_free(&ssl);
  mbedtls_ssl_config_free(&conf);
  mbedtls_ctr_drbg_free(&drbg);
  mbedtls_entropy_free(&entropy);

  JS_FreeCString(ctx, url);
  free(host); free(port); free(path);
  return blob;
}

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

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;
}
