/*
 * vault1984 CLI — HTTP/HTTPS client using BearSSL
 *
 * Supports both plain HTTP and HTTPS (TLS via BearSSL).
 * URL scheme determines protocol: http:// = plain, https:// = TLS.
 *
 * Trust anchors: TODO — currently no certificate validation.
 */

#define _POSIX_C_SOURCE 200809L

#include "http.h"

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

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#define SOCKCLOSE(s) closesocket(s)
typedef int socklen_t;
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#define SOCKET int
#define INVALID_SOCKET (-1)
#define SOCKCLOSE(s) close(s)
#endif

#include "bearssl.h"

/* --- System CA trust anchor loading --- */

/*
 * Load PEM CA certificates from system trust store.
 * Parses each certificate, extracts DN + public key → br_x509_trust_anchor.
 */

struct pem_accum {
    unsigned char *buf;
    size_t len;
    size_t cap;
};

static void pem_accum_push(void *dest_ctx, const void *src, size_t len) {
    struct pem_accum *a = (struct pem_accum *)dest_ctx;
    if (a->len + len > a->cap) {
        a->cap = (a->len + len) * 2;
        a->buf = realloc(a->buf, a->cap);
    }
    memcpy(a->buf + a->len, src, len);
    a->len += len;
}

/* DN callback for x509 decoder */
struct dn_accum {
    unsigned char *buf;
    size_t len;
    size_t cap;
};

static void dn_push(void *dest_ctx, const void *src, size_t len) {
    struct dn_accum *a = (struct dn_accum *)dest_ctx;
    if (a->len + len > a->cap) {
        a->cap = (a->len + len) * 2;
        a->buf = realloc(a->buf, a->cap);
    }
    memcpy(a->buf + a->len, src, len);
    a->len += len;
}

static void load_system_tas(br_x509_trust_anchor **out_tas, size_t *out_count) {
    *out_tas = NULL;
    *out_count = 0;

    static const char *ca_paths[] = {
        "/etc/ssl/certs/ca-certificates.crt",
        "/etc/pki/tls/certs/ca-bundle.crt",
        "/etc/ssl/cert.pem",
        "/usr/local/share/certs/ca-root-nss.crt",
        NULL
    };

    FILE *f = NULL;
    for (int i = 0; ca_paths[i]; i++) {
        f = fopen(ca_paths[i], "r");
        if (f) break;
    }
    if (!f) return;

    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);
    if (fsize <= 0 || fsize > 4 * 1024 * 1024) { fclose(f); return; }

    char *pem = malloc((size_t)fsize);
    size_t nr = fread(pem, 1, (size_t)fsize, f);
    fclose(f);

    /* Parse PEM → DER certificates → trust anchors */
    size_t ta_cap = 256;
    br_x509_trust_anchor *tas = calloc(ta_cap, sizeof(br_x509_trust_anchor));
    size_t ta_count = 0;

    br_pem_decoder_context pc;
    br_pem_decoder_init(&pc);

    struct pem_accum der = { malloc(8192), 0, 8192 };
    int in_cert = 0;

    size_t pos = 0;
    while (pos < nr) {
        size_t pushed = br_pem_decoder_push(&pc, pem + pos, nr - pos);
        pos += pushed;

        int event = br_pem_decoder_event(&pc);
        if (event == BR_PEM_BEGIN_OBJ) {
            in_cert = (strcmp(br_pem_decoder_name(&pc), "CERTIFICATE") == 0);
            der.len = 0;
            if (in_cert) br_pem_decoder_setdest(&pc, pem_accum_push, &der);
        } else if (event == BR_PEM_END_OBJ && in_cert && der.len > 0) {
            /* Decode X.509 certificate */
            struct dn_accum dn = { malloc(512), 0, 512 };
            br_x509_decoder_context dc;
            br_x509_decoder_init(&dc, dn_push, &dn);
            br_x509_decoder_push(&dc, der.buf, der.len);

            if (br_x509_decoder_last_error(&dc) == 0 && br_x509_decoder_isCA(&dc)) {
                br_x509_pkey *pk = br_x509_decoder_get_pkey(&dc);
                if (pk && ta_count < ta_cap) {
                    br_x509_trust_anchor *ta = &tas[ta_count];
                    ta->dn.data = dn.buf;
                    ta->dn.len = dn.len;
                    ta->flags = BR_X509_TA_CA;
                    ta->pkey = *pk;

                    /* Deep copy key data (decoder buffer will be reused) */
                    if (pk->key_type == BR_KEYTYPE_RSA) {
                        unsigned char *n = malloc(pk->key.rsa.nlen);
                        unsigned char *e = malloc(pk->key.rsa.elen);
                        memcpy(n, pk->key.rsa.n, pk->key.rsa.nlen);
                        memcpy(e, pk->key.rsa.e, pk->key.rsa.elen);
                        ta->pkey.key.rsa.n = n;
                        ta->pkey.key.rsa.e = e;
                    } else if (pk->key_type == BR_KEYTYPE_EC) {
                        unsigned char *q = malloc(pk->key.ec.qlen);
                        memcpy(q, pk->key.ec.q, pk->key.ec.qlen);
                        ta->pkey.key.ec.q = q;
                    }
                    ta_count++;
                    dn.buf = NULL; /* ownership transferred */
                }
            }
            free(dn.buf);
            in_cert = 0;
        } else if (event == BR_PEM_ERROR) {
            in_cert = 0;
        }
    }

    free(pem);
    free(der.buf);
    *out_tas = tas;
    *out_count = ta_count;
}

/* --- socket connect --- */

static SOCKET tcp_connect(const char *host, const char *port) {
#ifdef _WIN32
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);
#endif

    struct addrinfo hints, *res, *p;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    int err = getaddrinfo(host, port, &hints, &res);
    if (err != 0) {
        fprintf(stderr, "error: DNS lookup failed for %s: %s\n", host, gai_strerror(err));
        return INVALID_SOCKET;
    }

    SOCKET fd = INVALID_SOCKET;
    for (p = res; p; p = p->ai_next) {
        fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (fd == INVALID_SOCKET) continue;
        if (connect(fd, p->ai_addr, p->ai_addrlen) == 0) break;
        SOCKCLOSE(fd);
        fd = INVALID_SOCKET;
    }
    freeaddrinfo(res);

    if (fd == INVALID_SOCKET) {
        fprintf(stderr, "error: cannot connect to %s:%s\n", host, port);
    }
    return fd;
}

/* --- BearSSL I/O callbacks --- */

static int sock_read(void *ctx, unsigned char *buf, size_t len) {
    SOCKET fd = *(SOCKET *)ctx;
    int n;
    for (;;) {
        n = (int)recv(fd, (char *)buf, (int)len, 0);
        if (n <= 0) {
            if (n < 0 && errno == EINTR) continue;
            return -1;
        }
        return n;
    }
}

static int sock_write(void *ctx, const unsigned char *buf, size_t len) {
    SOCKET fd = *(SOCKET *)ctx;
    int n;
    for (;;) {
        n = (int)send(fd, (const char *)buf, (int)len, 0);
        if (n <= 0) {
            if (n < 0 && errno == EINTR) continue;
            return -1;
        }
        return n;
    }
}

/* --- plain socket send/recv wrappers --- */

static int plain_send_all(SOCKET fd, const char *data, size_t len) {
    size_t sent = 0;
    while (sent < len) {
        int n = (int)send(fd, data + sent, (int)(len - sent), 0);
        if (n <= 0) {
            if (n < 0 && errno == EINTR) continue;
            return -1;
        }
        sent += (size_t)n;
    }
    return 0;
}

static int plain_recv_all(SOCKET fd, char **out_buf, size_t *out_len) {
    size_t cap = 8192;
    size_t len = 0;
    char *buf = malloc(cap);
    if (!buf) return -1;

    for (;;) {
        if (len + 1024 > cap) {
            cap *= 2;
            char *tmp = realloc(buf, cap);
            if (!tmp) { free(buf); return -1; }
            buf = tmp;
        }
        int n = (int)recv(fd, buf + len, (int)(cap - len - 1), 0);
        if (n <= 0) break;
        len += (size_t)n;
    }
    buf[len] = '\0';
    *out_buf = buf;
    *out_len = len;
    return 0;
}

/* --- URL parsing --- */

struct parsed_url {
    char host[256];
    char port[8];
    char path[1024];
    int use_tls;
};

static int parse_url(const char *url, struct parsed_url *out) {
    memset(out, 0, sizeof(*out));

    if (strncmp(url, "https://", 8) == 0) {
        out->use_tls = 1;
        url += 8;
    } else if (strncmp(url, "http://", 7) == 0) {
        out->use_tls = 0;
        url += 7;
    } else {
        fprintf(stderr, "error: URL must start with http:// or https://\n");
        return -1;
    }

    /* host[:port]/path */
    const char *slash = strchr(url, '/');
    const char *colon = strchr(url, ':');

    if (colon && (!slash || colon < slash)) {
        size_t hlen = (size_t)(colon - url);
        if (hlen >= sizeof(out->host)) return -1;
        memcpy(out->host, url, hlen);
        out->host[hlen] = '\0';

        colon++;
        const char *pend = slash ? slash : (url + strlen(url));
        size_t plen = (size_t)(pend - colon);
        if (plen >= sizeof(out->port)) return -1;
        memcpy(out->port, colon, plen);
        out->port[plen] = '\0';
    } else {
        const char *hend = slash ? slash : (url + strlen(url));
        size_t hlen = (size_t)(hend - url);
        if (hlen >= sizeof(out->host)) return -1;
        memcpy(out->host, url, hlen);
        out->host[hlen] = '\0';
        strcpy(out->port, out->use_tls ? "443" : "80");
    }

    if (slash) {
        snprintf(out->path, sizeof(out->path), "%s", slash);
    } else {
        strcpy(out->path, "/");
    }

    return 0;
}

/* --- parse HTTP response --- */

static int parse_response(char *resp_buf, size_t resp_len, struct v84_response *resp) {
    if (resp_len < 12 || strncmp(resp_buf, "HTTP/", 5) != 0) {
        fprintf(stderr, "error: invalid HTTP response\n");
        free(resp_buf);
        return -1;
    }

    const char *sp = strchr(resp_buf, ' ');
    if (!sp) { free(resp_buf); return -1; }
    resp->status = atoi(sp + 1);

    const char *body = strstr(resp_buf, "\r\n\r\n");
    if (body) {
        body += 4;
        resp->body_len = resp_len - (size_t)(body - resp_buf);
        resp->body = malloc(resp->body_len + 1);
        if (resp->body) {
            memcpy(resp->body, body, resp->body_len);
            resp->body[resp->body_len] = '\0';
        }
    } else {
        resp->body = malloc(1);
        if (resp->body) resp->body[0] = '\0';
        resp->body_len = 0;
    }

    free(resp_buf);
    return 0;
}

/* --- HTTP GET (plain) --- */

static int http_get_plain(const struct parsed_url *pu, const char *bearer_token,
                          const char *agent_name, struct v84_response *resp) {
    SOCKET fd = tcp_connect(pu->host, pu->port);
    if (fd == INVALID_SOCKET) return -1;

    char request[2048];
    char agent_hdr[256] = "";
    if (agent_name && agent_name[0])
        snprintf(agent_hdr, sizeof(agent_hdr), "X-Agent: %s\r\n", agent_name);
    int reqlen = snprintf(request, sizeof(request),
        "GET %s HTTP/1.1\r\n"
        "Host: %s\r\n"
        "Authorization: Bearer %s\r\n"
        "%s"
        "Connection: close\r\n"
        "Accept: application/json\r\n"
        "\r\n",
        pu->path, pu->host, bearer_token, agent_hdr);

    if (plain_send_all(fd, request, (size_t)reqlen) != 0) {
        fprintf(stderr, "error: send failed\n");
        SOCKCLOSE(fd);
        return -1;
    }

    char *resp_buf = NULL;
    size_t resp_len = 0;
    if (plain_recv_all(fd, &resp_buf, &resp_len) != 0) {
        fprintf(stderr, "error: recv failed\n");
        SOCKCLOSE(fd);
        return -1;
    }

    SOCKCLOSE(fd);
    return parse_response(resp_buf, resp_len, resp);
}

/* --- HTTPS GET (BearSSL TLS) --- */

static int http_get_tls(const struct parsed_url *pu, const char *bearer_token,
                        const char *agent_name, struct v84_response *resp) {
    SOCKET fd = tcp_connect(pu->host, pu->port);
    if (fd == INVALID_SOCKET) return -1;

    br_ssl_client_context sc;
    br_x509_minimal_context xc;
    unsigned char iobuf[BR_SSL_BUFSIZE_BIDI];

    /*
     * Load system CA certificates for TLS validation.
     * Falls back to no-validation mode if CAs can't be loaded.
     */
    size_t ta_count = 0;
    br_x509_trust_anchor *tas = NULL;
    load_system_tas(&tas, &ta_count);

    br_x509_minimal_init(&xc, &br_sha256_vtable, tas, ta_count);
    br_x509_minimal_set_rsa(&xc, &br_rsa_i31_pkcs1_vrfy);
    br_x509_minimal_set_ecdsa(&xc, &br_ec_prime_i31, &br_ecdsa_i31_vrfy_asn1);

    br_ssl_client_init_full(&sc, &xc, tas, ta_count);
    br_ssl_engine_set_buffer(&sc.eng, iobuf, sizeof(iobuf), 1);
    br_ssl_client_reset(&sc, pu->host, 0);

    br_sslio_context ioc;
    br_sslio_init(&ioc, &sc.eng, sock_read, &fd, sock_write, &fd);

    char request[2048];
    char agent_hdr[256] = "";
    if (agent_name && agent_name[0])
        snprintf(agent_hdr, sizeof(agent_hdr), "X-Agent: %s\r\n", agent_name);
    int reqlen = snprintf(request, sizeof(request),
        "GET %s HTTP/1.1\r\n"
        "Host: %s\r\n"
        "Authorization: Bearer %s\r\n"
        "%s"
        "Connection: close\r\n"
        "Accept: application/json\r\n"
        "\r\n",
        pu->path, pu->host, bearer_token, agent_hdr);

    if (br_sslio_write_all(&ioc, request, (size_t)reqlen) != 0) {
        fprintf(stderr, "error: TLS write failed\n");
        int err = br_ssl_engine_last_error(&sc.eng);
        if (err) fprintf(stderr, "  BearSSL error: %d\n", err);
        SOCKCLOSE(fd);
        return -1;
    }
    br_sslio_flush(&ioc);

    size_t resp_cap = 8192;
    size_t resp_len = 0;
    char *resp_buf = malloc(resp_cap);
    if (!resp_buf) { SOCKCLOSE(fd); return -1; }

    for (;;) {
        if (resp_len + 1024 > resp_cap) {
            resp_cap *= 2;
            char *tmp = realloc(resp_buf, resp_cap);
            if (!tmp) { free(resp_buf); SOCKCLOSE(fd); return -1; }
            resp_buf = tmp;
        }
        int n = br_sslio_read(&ioc, resp_buf + resp_len, resp_cap - resp_len - 1);
        if (n <= 0) break;
        resp_len += (size_t)n;
    }
    resp_buf[resp_len] = '\0';

    SOCKCLOSE(fd);
    return parse_response(resp_buf, resp_len, resp);
}

/* --- public API --- */

int http_get(const char *url, const char *bearer_token, const char *agent_name,
             struct v84_response *resp) {
    memset(resp, 0, sizeof(*resp));

    struct parsed_url pu;
    if (parse_url(url, &pu) != 0) return -1;

    if (pu.use_tls) {
        return http_get_tls(&pu, bearer_token, agent_name, resp);
    } else {
        return http_get_plain(&pu, bearer_token, agent_name, resp);
    }
}
