#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "el_runtime.h"

el_val_t http_get_auth(el_val_t url, el_val_t tok) {
    char bearer[2048]; snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(tok));
    el_val_t hdr_val = EL_STR(bearer);
    el_val_t headers = el_map_new(1, "Authorization", hdr_val);
    return http_get_with_headers(url, headers);
}
/* supabase_get — GET from Supabase REST API with both apikey and Bearer headers */
el_val_t supabase_get(el_val_t project_url, el_val_t service_key, el_val_t table_and_query) {
    char bearer[2048]; snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(service_key));
    char url[2048]; snprintf(url, sizeof(url), "%s/rest/v1/%s", EL_CSTR(project_url), EL_CSTR(table_and_query));
    el_val_t headers = el_map_new(3,
        "Authorization", EL_STR(bearer),
        "apikey",        service_key,
        "Accept",        EL_STR("application/json"));
    return http_get_with_headers(EL_STR(url), headers);
}
el_val_t http_post_auth(el_val_t url, el_val_t tok, el_val_t body) {
    /* Bearer-only. No Content-Type — Stripe's REST API consumes
     * application/x-www-form-urlencoded bodies and is the heaviest
     * caller of this helper. JSON callers (Resend, Anthropic, etc.) must
     * use http_post_auth_json below, which sets Content-Type properly.
     * Mixing Stripe form-POST and Resend JSON-POST through one helper was
     * the root cause of the silent-drop bug — Resend 422s with "Missing
     * `to` field" if the body arrives without Content-Type:
     * application/json (it parses as form-encoded otherwise). */
    char bearer[2048]; snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(tok));
    el_val_t hdr_val = EL_STR(bearer);
    el_val_t headers = el_map_new(1, "Authorization", hdr_val);
    return http_post_with_headers(url, body, headers);
}

/* http_post_auth_json — Bearer + Content-Type: application/json.
 * Use for any REST API that consumes JSON request bodies (Resend,
 * Anthropic, OpenAI, etc.). Returns the response body, or "" on
 * transport failure. HTTP 4xx/5xx are NOT swallowed — the body is
 * still returned so callers can parse error JSON. */
el_val_t http_post_auth_json(el_val_t url, el_val_t tok, el_val_t body) {
    char bearer[2048]; snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(tok));
    el_val_t headers = el_map_new(2,
        "Authorization", EL_STR(bearer),
        "Content-Type",  EL_STR("application/json"));
    return http_post_with_headers(url, body, headers);
}
/*
 * http_delete_auth - DELETE with Bearer auth + apikey (Supabase-style).
 * The runtime exposes http_delete(url) but no header variant, so we
 * implement DELETE-with-headers via http_post_with_headers using a
 * libcurl-backed helper; falling back to http_delete if no auth available.
 *
 * For Supabase REST DELETE we need both Authorization (user JWT) AND
 * apikey (anon key) - the apikey is required to reach PostgREST at all,
 * the Bearer JWT is what auth.uid() checks for the RLS policy. The
 * runtime's http_post_with_headers takes a method-agnostic body; we
 * use libcurl directly via a tiny shim.
 */
#include <curl/curl.h>
typedef struct { char *buf; size_t len; size_t cap; } _stub_resp_t;
static size_t _stub_write(void *ptr, size_t size, size_t nmemb, void *u) {
    _stub_resp_t *r = (_stub_resp_t*)u;
    size_t add = size * nmemb;
    if (r->len + add + 1 > r->cap) {
        size_t nc = (r->cap ? r->cap : 4096);
        while (nc < r->len + add + 1) nc *= 2;
        char *nb = realloc(r->buf, nc);
        if (!nb) return 0;
        r->buf = nb; r->cap = nc;
    }
    memcpy(r->buf + r->len, ptr, add);
    r->len += add;
    r->buf[r->len] = '\0';
    return add;
}
el_val_t http_delete_auth(el_val_t url, el_val_t bearer_tok, el_val_t apikey) {
    CURL *c = curl_easy_init();
    if (!c) return EL_STR("");
    char auth_hdr[2048]; snprintf(auth_hdr, sizeof(auth_hdr), "Authorization: Bearer %s", EL_CSTR(bearer_tok));
    char api_hdr[2048];  snprintf(api_hdr,  sizeof(api_hdr),  "apikey: %s", EL_CSTR(apikey));
    struct curl_slist *hdrs = NULL;
    hdrs = curl_slist_append(hdrs, auth_hdr);
    hdrs = curl_slist_append(hdrs, api_hdr);
    hdrs = curl_slist_append(hdrs, "Accept: application/json");
    _stub_resp_t r = {0};
    curl_easy_setopt(c, CURLOPT_URL, EL_CSTR(url));
    curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(c, CURLOPT_HTTPHEADER, hdrs);
    curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
    curl_easy_setopt(c, CURLOPT_TIMEOUT, 60L);
    curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, _stub_write);
    curl_easy_setopt(c, CURLOPT_WRITEDATA, &r);
    CURLcode rc = curl_easy_perform(c);
    long http_code = 0;
    curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &http_code);
    curl_easy_cleanup(c);
    curl_slist_free_all(hdrs);
    if (rc != CURLE_OK) {
        free(r.buf);
        return EL_STR("");
    }
    if (http_code >= 400) {
        free(r.buf);
        return EL_STR("");
    }
    if (!r.buf) return EL_STR("");
    /* Note: small leak by design - response lives until process exits.
     * Mirrors the existing pattern in supabase_auth_user / gcs_get_token. */
    return EL_STR(r.buf);
}

/*
 * supabase_upsert_user - POST a row to a Supabase table on behalf of a
 * logged-in user. Sends Authorization: Bearer <user-jwt> + apikey: <anon>
 * + Prefer: resolution=merge-duplicates so RLS treats this as the user's
 * own write and PostgREST upserts on the (composite) primary key.
 *
 * `table_and_query` is the path after /rest/v1/, e.g.
 *   "share_votes?on_conflict=share_id,user_id"
 *
 * Returns the response body (often "" with Prefer=return=minimal) or "" on
 * non-2xx / network failure. Callers should re-fetch aggregate state via a
 * separate GET to confirm the write landed.
 */
el_val_t supabase_upsert_user(el_val_t project_url, el_val_t anon_key,
                              el_val_t user_jwt, el_val_t table_and_query,
                              el_val_t row_json) {
    char url[1024];
    snprintf(url, sizeof(url), "%s/rest/v1/%s", EL_CSTR(project_url), EL_CSTR(table_and_query));
    char bearer[2048];
    snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(user_jwt));
    el_val_t headers = el_map_new(5,
        "Authorization", EL_STR(bearer),
        "apikey",        anon_key,
        "Content-Type",  EL_STR("application/json"),
        "Prefer",        EL_STR("return=minimal,resolution=merge-duplicates"),
        "Accept",        EL_STR("application/json"));
    return http_post_with_headers(EL_STR(url), row_json, headers);
}
/*
 * supabase_auth_user — verify a Supabase user JWT and return the user object.
 * Calls GET {project_url}/auth/v1/user with both apikey and Authorization headers.
 * Returns the raw user JSON, or "" on failure.
 */
el_val_t supabase_auth_user(el_val_t project_url, el_val_t anon_key, el_val_t user_jwt) {
    char bearer[2048];
    snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(user_jwt));
    char url[512];
    snprintf(url, sizeof(url), "%s/auth/v1/user", EL_CSTR(project_url));
    el_val_t headers = el_map_new(3,
        "Authorization", EL_STR(bearer),
        "apikey",        anon_key,
        "Accept",        EL_STR("application/json"));
    el_val_t resp = http_get_with_headers(EL_STR(url), headers);
    if (!resp) return EL_STR("");
    return resp;
}

el_val_t cwd(void) {
    static char buf[4096];
    if (!getcwd(buf, sizeof(buf))) buf[0] = '\0';
    return EL_STR(buf);
}
el_val_t color_bold(el_val_t s) { return s; }
/* unix_timestamp is now provided by el_runtime.c (back-compat shim).
 * Keeping the stub here would produce a duplicate symbol at link time. */

/*
 * supabase_insert — POST a JSON row to a Supabase table via the REST API.
 * Requires both apikey and Authorization: Bearer headers (Supabase convention).
 * Returns the response body, or "" on failure.
 */
el_val_t supabase_insert(el_val_t project_url, el_val_t service_key, el_val_t table, el_val_t row_json) {
    char url[512];
    snprintf(url, sizeof(url), "%s/rest/v1/%s", EL_CSTR(project_url), EL_CSTR(table));
    char bearer[2048];
    snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(service_key));
    el_val_t headers = el_map_new(4,
        "Authorization",  EL_STR(bearer),
        "apikey",         service_key,
        "Content-Type",   EL_STR("application/json"),
        "Prefer",         EL_STR("return=minimal,resolution=merge-duplicates"));
    return http_post_with_headers(EL_STR(url), row_json, headers);
}

/*
 * supabase_admin_invite — POST {project_url}/auth/v1/invite with the
 * service-role key to create-or-find a user and email them a magic-link
 * invitation. body_json should look like:
 *   {"email":"...","data":{"name":"..."},"redirect_to":"..."}
 * Returns the JSON response (contains the new/existing user's id under "id").
 *
 * Used by the Stripe webhook to auto-provision a Supabase account whenever
 * payment_intent.succeeded or setup_intent.succeeded fires - so every buyer
 * lands on /account with an account waiting for them, regardless of whether
 * they signed in before payment.
 */
el_val_t supabase_admin_invite(el_val_t project_url, el_val_t service_key, el_val_t body_json) {
    char url[512];
    snprintf(url, sizeof(url), "%s/auth/v1/invite", EL_CSTR(project_url));
    char bearer[2048];
    snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(service_key));
    el_val_t headers = el_map_new(3,
        "Authorization",  EL_STR(bearer),
        "apikey",         service_key,
        "Content-Type",   EL_STR("application/json"));
    return http_post_with_headers(EL_STR(url), body_json, headers);
}

/*
 * gcs_get_token — fetch an OAuth2 bearer token.
 *
 * Priority:
 *  1. GOOGLE_ACCESS_TOKEN env var (local dev: `gcloud auth print-access-token`)
 *  2. GCP metadata server (Cloud Run, automatic SA credentials)
 *
 * Returns the raw token string, or "" on failure.
 */
static el_val_t gcs_get_token(void) {
    /* 1. Env override (local dev) */
    const char *env_tok = getenv("GOOGLE_ACCESS_TOKEN");
    if (env_tok && *env_tok) return EL_STR(env_tok);

    /* 2. Metadata server */
    el_val_t meta_url = EL_STR(
        "http://metadata.google.internal/computeMetadata/v1/instance"
        "/service-accounts/default/token");
    el_val_t meta_headers = el_map_new(1, "Metadata-Flavor", EL_STR("Google"));
    el_val_t resp = http_get_with_headers(meta_url, meta_headers);
    if (!resp) return EL_STR("");

    /* Parse access_token field from JSON response */
    const char *s = EL_CSTR(resp);
    const char *key = "\"access_token\":\"";
    const char *p = strstr(s, key);
    if (!p) return EL_STR("");
    p += strlen(key);
    const char *end = strchr(p, '"');
    if (!end) return EL_STR("");

    size_t len = (size_t)(end - p);
    char *tok = malloc(len + 1);
    memcpy(tok, p, len);
    tok[len] = '\0';
    el_val_t result = EL_STR(tok);
    /* Note: intentional small leak — token lives for process lifetime */
    return result;
}

/*
 * gcs_write — upload content to a GCS object.
 *
 * Uses the GCS JSON upload API (POST, media upload type).
 * Returns "ok" on success, "" on failure.
 */
el_val_t gcs_write(el_val_t bucket, el_val_t object_name, el_val_t content) {
    el_val_t tok = gcs_get_token();
    if (!tok || !*EL_CSTR(tok)) return EL_STR("");

    /* URL-encode the object name (replace / with %2F) */
    const char *obj = EL_CSTR(object_name);
    char encoded[1024];
    size_t ei = 0;
    for (size_t i = 0; obj[i] && ei < sizeof(encoded) - 4; i++) {
        if (obj[i] == '/') { encoded[ei++] = '%'; encoded[ei++] = '2'; encoded[ei++] = 'F'; }
        else encoded[ei++] = obj[i];
    }
    encoded[ei] = '\0';

    /* Build upload URL */
    char url[2048];
    snprintf(url, sizeof(url),
        "https://storage.googleapis.com/upload/storage/v1/b/%s/o"
        "?uploadType=media&name=%s",
        EL_CSTR(bucket), encoded);

    /* POST with Authorization + Content-Type headers */
    char bearer[2048];
    snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(tok));
    el_val_t headers = el_map_new(2,
        "Authorization",  EL_STR(bearer),
        "Content-Type",   EL_STR("application/json"));
    el_val_t resp = http_post_with_headers(EL_STR(url), content, headers);
    if (!resp) return EL_STR("");

    /* GCS returns the object metadata JSON on success */
    const char *r = EL_CSTR(resp);
    if (strstr(r, "\"name\"")) return EL_STR("ok");
    return EL_STR("");
}

/*
 * gcs_read — download a GCS object's content.
 *
 * Returns the raw content, or "" if the object does not exist or on error.
 */
el_val_t gcs_read(el_val_t bucket, el_val_t object_name) {
    el_val_t tok = gcs_get_token();
    if (!tok || !*EL_CSTR(tok)) return EL_STR("");

    /* URL-encode object name */
    const char *obj = EL_CSTR(object_name);
    char encoded[1024];
    size_t ei = 0;
    for (size_t i = 0; obj[i] && ei < sizeof(encoded) - 4; i++) {
        if (obj[i] == '/') { encoded[ei++] = '%'; encoded[ei++] = '2'; encoded[ei++] = 'F'; }
        else encoded[ei++] = obj[i];
    }
    encoded[ei] = '\0';

    char url[2048];
    snprintf(url, sizeof(url),
        "https://storage.googleapis.com/download/storage/v1/b/%s/o/%s?alt=media",
        EL_CSTR(bucket), encoded);

    char bearer[2048];
    snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(tok));
    el_val_t headers = el_map_new(1, "Authorization", EL_STR(bearer));
    el_val_t resp = http_get_with_headers(EL_STR(url), headers);
    if (!resp) return EL_STR("");
    return resp;
}
