#include "bsphp_crypto.h"

#include <windows.h>
#include <bcrypt.h>
#include <wincrypt.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

static char g_bsphp_crypto_last_error[256] = "";

static void bsphp_set_last_error(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(g_bsphp_crypto_last_error, sizeof(g_bsphp_crypto_last_error), fmt, ap);
    va_end(ap);
}

const char *bsphp_crypto_last_error(void) {
    return g_bsphp_crypto_last_error;
}

static char *b64_encode_alloc(const unsigned char *data, int data_len) {
    DWORD out_len = 0;
    char *out = NULL;
    if (!CryptBinaryToStringA(data, (DWORD)data_len, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &out_len)) return NULL;
    out = (char *)malloc(out_len + 1);
    if (!out) return NULL;
    if (!CryptBinaryToStringA(data, (DWORD)data_len, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, out, &out_len)) {
        free(out);
        return NULL;
    }
    return out;
}

static int b64_decode_alloc(const char *b64, unsigned char **out, int *out_len) {
    DWORD need = 0;
    unsigned char *buf = NULL;
    if (!CryptStringToBinaryA(b64, 0, CRYPT_STRING_BASE64_ANY, NULL, &need, NULL, NULL)) return 0;
    buf = (unsigned char *)malloc(need + 1);
    if (!buf) return 0;
    if (!CryptStringToBinaryA(b64, 0, CRYPT_STRING_BASE64_ANY, buf, &need, NULL, NULL)) {
        free(buf);
        return 0;
    }
    *out = buf;
    *out_len = (int)need;
    return 1;
}

static int b64_decode_alloc_cipher(const char *b64, unsigned char **out, int *out_len) {
    DWORD need = 0;
    unsigned char *buf = NULL;
    size_t n = strlen(b64);
    while (n > 0 && (b64[n - 1] == '\r' || b64[n - 1] == '\n' || b64[n - 1] == ' ' || b64[n - 1] == '\t'))
        n--;
    if (!CryptStringToBinaryA(b64, (DWORD)n, CRYPT_STRING_BASE64, NULL, &need, NULL, NULL)) return 0;
    buf = (unsigned char *)malloc(need + 1);
    if (!buf) return 0;
    if (!CryptStringToBinaryA(b64, (DWORD)n, CRYPT_STRING_BASE64, buf, &need, NULL, NULL)) {
        free(buf);
        return 0;
    }
    *out = buf;
    *out_len = (int)need;
    return 1;
}

static char *strip_pem_to_base64(const char *pem_or_b64) {
    size_t n = strlen(pem_or_b64);
    char *out = (char *)malloc(n + 1);
    const unsigned char *p = (const unsigned char *)pem_or_b64;
    size_t j = 0;
    if (!out) return NULL;
    /* 支持超长单行 Base64（配置中的 key 就是这种形式），不能按 512 截断 */
    while (*p) {
        unsigned char c = *p++;
        if (isalnum(c) || c == '+' || c == '/' || c == '=') {
            out[j++] = (char)c;
        }
    }
    out[j] = '\0';
    return out;
}

int bsphp_md5_hex(const char *input, char out_hex[33]) {
    HCRYPTPROV hProv = 0;
    HCRYPTHASH hHash = 0;
    BYTE digest[16];
    DWORD digest_len = sizeof(digest);
    int i;
    if (!CryptAcquireContextA(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) return 0;
    if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
        CryptReleaseContext(hProv, 0);
        return 0;
    }
    if (!CryptHashData(hHash, (const BYTE *)input, (DWORD)strlen(input), 0)) {
        CryptDestroyHash(hHash);
        CryptReleaseContext(hProv, 0);
        return 0;
    }
    if (!CryptGetHashParam(hHash, HP_HASHVAL, digest, &digest_len, 0)) {
        CryptDestroyHash(hHash);
        CryptReleaseContext(hProv, 0);
        return 0;
    }
    for (i = 0; i < 16; i++) sprintf(out_hex + i * 2, "%02x", digest[i]);
    out_hex[32] = '\0';
    CryptDestroyHash(hHash);
    CryptReleaseContext(hProv, 0);
    return 1;
}

int bsphp_aes128_cbc_encrypt_base64(const char *plaintext, const char key16[17], char **out_b64) {
    HCRYPTPROV hProv = 0;
    HCRYPTKEY hKey = 0;
    DWORD mode = CRYPT_MODE_CBC;
    BYTE iv[16];
    DWORD in_len = (DWORD)strlen(plaintext);
    DWORD cap = in_len + 32;
    BYTE *buf = NULL;
    struct {
        BLOBHEADER hdr;
        DWORD cbKeySize;
        BYTE key[16];
    } key_blob;
    int ok = 0;

    memset(&key_blob, 0, sizeof(key_blob));
    key_blob.hdr.bType = PLAINTEXTKEYBLOB;
    key_blob.hdr.bVersion = CUR_BLOB_VERSION;
    key_blob.hdr.aiKeyAlg = CALG_AES_128;
    key_blob.cbKeySize = 16;
    memcpy(key_blob.key, key16, 16);
    memcpy(iv, key16, 16);

    if (!CryptAcquireContextA(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) goto done;
    if (!CryptImportKey(hProv, (BYTE *)&key_blob, sizeof(key_blob), 0, 0, &hKey)) goto done;
    if (!CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&mode, 0)) goto done;
    if (!CryptSetKeyParam(hKey, KP_IV, iv, 0)) goto done;

    buf = (BYTE *)malloc(cap);
    if (!buf) goto done;
    memcpy(buf, plaintext, in_len);
    if (!CryptEncrypt(hKey, 0, TRUE, 0, buf, &in_len, cap)) goto done;
    *out_b64 = b64_encode_alloc(buf, (int)in_len);
    ok = (*out_b64 != NULL);

done:
    if (buf) free(buf);
    if (hKey) CryptDestroyKey(hKey);
    if (hProv) CryptReleaseContext(hProv, 0);
    return ok;
}

int bsphp_aes128_cbc_decrypt_base64(const char *ciphertext_b64, const char key16[17], char **out_plain) {
    HCRYPTPROV hProv = 0;
    HCRYPTKEY hKey = 0;
    DWORD mode = CRYPT_MODE_CBC;
    BYTE iv[16];
    BYTE *buf = NULL;
    int buf_len = 0;
    DWORD data_len = 0;
    struct {
        BLOBHEADER hdr;
        DWORD cbKeySize;
        BYTE key[16];
    } key_blob;
    int ok = 0;

    memset(&key_blob, 0, sizeof(key_blob));
    key_blob.hdr.bType = PLAINTEXTKEYBLOB;
    key_blob.hdr.bVersion = CUR_BLOB_VERSION;
    key_blob.hdr.aiKeyAlg = CALG_AES_128;
    key_blob.cbKeySize = 16;
    memcpy(key_blob.key, key16, 16);
    memcpy(iv, key16, 16);

    if (!b64_decode_alloc(ciphertext_b64, &buf, &buf_len)) goto done;
    data_len = (DWORD)buf_len;
    if (!CryptAcquireContextA(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) goto done;
    if (!CryptImportKey(hProv, (BYTE *)&key_blob, sizeof(key_blob), 0, 0, &hKey)) goto done;
    if (!CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&mode, 0)) goto done;
    if (!CryptSetKeyParam(hKey, KP_IV, iv, 0)) goto done;
    if (!CryptDecrypt(hKey, 0, TRUE, 0, buf, &data_len)) goto done;

    *out_plain = (char *)malloc(data_len + 1);
    if (!*out_plain) goto done;
    memcpy(*out_plain, buf, data_len);
    (*out_plain)[data_len] = '\0';
    ok = 1;

done:
    if (!ok && *out_plain) {
        free(*out_plain);
        *out_plain = NULL;
    }
    if (buf) free(buf);
    if (hKey) CryptDestroyKey(hKey);
    if (hProv) CryptReleaseContext(hProv, 0);
    return ok;
}

int bsphp_rsa_encrypt_pkcs1_base64(const char *message, const char *public_key_b64_or_pem, char **out_b64) {
    DWORD der_len = 0;
    BYTE *der = NULL;
    DWORD info_len = 0;
    CERT_PUBLIC_KEY_INFO *pub_info = NULL;
    BCRYPT_KEY_HANDLE h_pub = NULL;
    DWORD out_len = 0;
    DWORD msg_len = (DWORD)strlen(message);
    BYTE *cipher = NULL;
    int ok = 0;

    if (!CryptStringToBinaryA(public_key_b64_or_pem, 0, CRYPT_STRING_ANY, NULL, &der_len, NULL, NULL)) goto done;
    der = (BYTE *)malloc(der_len);
    if (!der) goto done;
    if (!CryptStringToBinaryA(public_key_b64_or_pem, 0, CRYPT_STRING_ANY, der, &der_len, NULL, NULL)) goto done;

    if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
                             der, der_len, CRYPT_DECODE_ALLOC_FLAG,
                             NULL, &pub_info, &info_len)) goto done;

    if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, pub_info, 0, NULL, &h_pub)) goto done;

    if (BCryptEncrypt(h_pub, (PUCHAR)message, msg_len, NULL, NULL, 0, NULL, 0, &out_len, BCRYPT_PAD_PKCS1) != 0) goto done;
    cipher = (BYTE *)malloc(out_len);
    if (!cipher) goto done;
    if (BCryptEncrypt(h_pub, (PUCHAR)message, msg_len, NULL, NULL, 0, cipher, out_len, &out_len, BCRYPT_PAD_PKCS1) != 0) goto done;

    *out_b64 = b64_encode_alloc(cipher, (int)out_len);
    ok = (*out_b64 != NULL);

done:
    if (cipher) free(cipher);
    if (h_pub) BCryptDestroyKey(h_pub);
    if (pub_info) LocalFree(pub_info);
    if (der) free(der);
    return ok;
}

typedef struct {
    unsigned char *p;
    size_t len;
} bsphp_asn1_int;

static void bsphp_asn1_int_clear(bsphp_asn1_int *v) {
    if (v->p) {
        free(v->p);
        v->p = NULL;
    }
    v->len = 0;
}

static int bsphp_read_asn1_integer(const unsigned char *der, size_t len, size_t *i, bsphp_asn1_int *out) {
    size_t pos = *i;
    if (pos >= len || der[pos] != 0x02) return 0;
    pos++;
    size_t ilen = der[pos++];
    if (ilen & 0x80) {
        int nb = (int)(ilen & 0x7f);
        ilen = 0;
        for (int j = 0; j < nb && pos < len; j++) ilen = (ilen << 8) | der[pos++];
    }
    if (pos + ilen > len) return 0;
    out->p = (unsigned char *)malloc(ilen);
    if (!out->p) return 0;
    memcpy(out->p, der + pos, ilen);
    out->len = ilen;
    if (out->len > 0 && out->p[0] == 0) {
        memmove(out->p, out->p + 1, out->len - 1);
        out->len--;
    }
    *i = pos + ilen;
    return 1;
}

static int bsphp_asn1_pad_to(bsphp_asn1_int *v, size_t target) {
    if (!v || !target) return 0;
    if (v->len > target) return 0;
    if (v->len == target) return 1;
    {
        size_t pad = target - v->len;
        unsigned char *np = (unsigned char *)malloc(target);
        if (!np) return 0;
        memset(np, 0, pad);
        memcpy(np + pad, v->p, v->len);
        free(v->p);
        v->p = np;
        v->len = target;
    }
    return 1;
}

static void bsphp_clear_rsa8(bsphp_asn1_int *n, bsphp_asn1_int *e, bsphp_asn1_int *d,
    bsphp_asn1_int *pr1, bsphp_asn1_int *pr2, bsphp_asn1_int *e1, bsphp_asn1_int *e2, bsphp_asn1_int *cf) {
    bsphp_asn1_int_clear(n);
    bsphp_asn1_int_clear(e);
    bsphp_asn1_int_clear(d);
    bsphp_asn1_int_clear(pr1);
    bsphp_asn1_int_clear(pr2);
    bsphp_asn1_int_clear(e1);
    bsphp_asn1_int_clear(e2);
    bsphp_asn1_int_clear(cf);
}

static int bsphp_parse_pkcs1_rsa_private(const unsigned char *der, size_t len,
    bsphp_asn1_int *n, bsphp_asn1_int *e, bsphp_asn1_int *d,
    bsphp_asn1_int *pr1, bsphp_asn1_int *pr2, bsphp_asn1_int *e1, bsphp_asn1_int *e2, bsphp_asn1_int *cf) {
    size_t i = 0;
    bsphp_clear_rsa8(n, e, d, pr1, pr2, e1, e2, cf);
    if (len < 2 || der[0] != 0x30) return 0;
    i = 1;
    {
        size_t seqLen = der[i++];
        if (seqLen & 0x80) {
            int nb = (int)(seqLen & 0x7f);
            seqLen = 0;
            for (int j = 0; j < nb && i < len; j++) seqLen = (seqLen << 8) | der[i++];
        }
        (void)seqLen;
    }
    {
        bsphp_asn1_int ver = {0};
        if (!bsphp_read_asn1_integer(der, len, &i, &ver)) return 0;
        bsphp_asn1_int_clear(&ver);
    }
    if (!bsphp_read_asn1_integer(der, len, &i, n)) return 0;
    if (!bsphp_read_asn1_integer(der, len, &i, e)) return 0;
    if (!bsphp_read_asn1_integer(der, len, &i, d)) return 0;
    if (!bsphp_read_asn1_integer(der, len, &i, pr1)) return 0;
    if (!bsphp_read_asn1_integer(der, len, &i, pr2)) return 0;
    if (!bsphp_read_asn1_integer(der, len, &i, e1)) return 0;
    if (!bsphp_read_asn1_integer(der, len, &i, e2)) return 0;
    if (!bsphp_read_asn1_integer(der, len, &i, cf)) return 0;
    return n->len && e->len && d->len && pr1->len && pr2->len;
}

static int bsphp_parse_pkcs8_rsa_private(const unsigned char *der, size_t len,
    bsphp_asn1_int *n, bsphp_asn1_int *e, bsphp_asn1_int *d,
    bsphp_asn1_int *pr1, bsphp_asn1_int *pr2, bsphp_asn1_int *e1, bsphp_asn1_int *e2, bsphp_asn1_int *cf) {
    size_t i = 0;
    bsphp_clear_rsa8(n, e, d, pr1, pr2, e1, e2, cf);
    if (len < 2 || der[0] != 0x30) return 0;
    i = 1;
    {
        size_t seqLen = der[i++];
        if (seqLen & 0x80) {
            int nb = (int)(seqLen & 0x7f);
            seqLen = 0;
            for (int j = 0; j < nb && i < len; j++) seqLen = (seqLen << 8) | der[i++];
        }
        (void)seqLen;
    }
    if (i >= len || der[i++] != 0x02) return 0;
    {
        size_t vlen = der[i++];
        i += vlen;
    }
    if (i >= len || der[i++] != 0x30) return 0;
    {
        size_t alen = der[i++];
        if (alen & 0x80) {
            int nb = (int)(alen & 0x7f);
            alen = 0;
            for (int j = 0; j < nb && i < len; j++) alen = (alen << 8) | der[i++];
        }
        i += alen;
    }
    if (i >= len || der[i++] != 0x04) return 0;
    {
        size_t olen = der[i++];
        if (olen & 0x80) {
            int nb = (int)(olen & 0x7f);
            olen = 0;
            for (int j = 0; j < nb && i < len; j++) olen = (olen << 8) | der[i++];
        }
        if (i + olen > len) return 0;
        return bsphp_parse_pkcs1_rsa_private(der + i, olen, n, e, d, pr1, pr2, e1, e2, cf);
    }
}

static int rsa_decrypt_bcrypt(const unsigned char *cipher, int cipher_len,
    const unsigned char *key_der, int key_der_len, char **out_plain) {
    bsphp_asn1_int n = {0}, e = {0}, d = {0}, p = {0}, q = {0}, e1 = {0}, e2 = {0}, cf = {0};
    int ok = 0;
    BCRYPT_ALG_HANDLE hAlg = NULL;
    BCRYPT_KEY_HANDLE hKey = NULL;
    unsigned char *blob = NULL;
    DWORD cbBlob = 0;
    NTSTATUS status;
    unsigned char *pt = NULL;
    DWORD cb = 0;

    if (key_der_len <= 0 || !key_der || key_der[0] != 0x30) {
        bsphp_set_last_error("invalid private key der");
        goto done;
    }

    if (!bsphp_parse_pkcs8_rsa_private(key_der, (size_t)key_der_len, &n, &e, &d, &p, &q, &e1, &e2, &cf)) {
        bsphp_clear_rsa8(&n, &e, &d, &p, &q, &e1, &e2, &cf);
        if (!bsphp_parse_pkcs1_rsa_private(key_der, (size_t)key_der_len, &n, &e, &d, &p, &q, &e1, &e2, &cf)) {
            bsphp_set_last_error("parse pkcs8/pkcs1 private key failed");
            goto done;
        }
    }

    if (!n.p || !e.p || !p.p || !q.p) {
        bsphp_set_last_error("missing RSA components n/e/p/q");
        goto done;
    }

    {
        size_t cbMod = n.len;
        size_t half = (cbMod + 1) / 2;
        if (!bsphp_asn1_pad_to(&p, half)) goto done;
        if (!bsphp_asn1_pad_to(&q, half)) goto done;
        /* RSAPRIVATE_BLOB 只需要 p,q；其余 CRT 参数在不同平台容错不一致，避免引入导入差异 */
    }

    cbBlob = sizeof(BCRYPT_RSAKEY_BLOB) + (DWORD)(e.len + n.len + p.len + q.len);
    blob = (unsigned char *)malloc(cbBlob);
    if (!blob) goto done;
    {
        BCRYPT_RSAKEY_BLOB *hdr = (BCRYPT_RSAKEY_BLOB *)blob;
        hdr->Magic = BCRYPT_RSAPRIVATE_MAGIC;
        hdr->BitLength = (DWORD)(n.len * 8);
        hdr->cbPublicExp = (DWORD)e.len;
        hdr->cbModulus = (DWORD)n.len;
        hdr->cbPrime1 = (DWORD)p.len;
        hdr->cbPrime2 = (DWORD)q.len;
        {
            size_t off = sizeof(BCRYPT_RSAKEY_BLOB);
            memcpy(blob + off, e.p, e.len);
            off += e.len;
            memcpy(blob + off, n.p, n.len);
            off += n.len;
            memcpy(blob + off, p.p, p.len);
            off += p.len;
            memcpy(blob + off, q.p, q.len);
            off += q.len;
        }
    }

    status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RSA_ALGORITHM, NULL, 0);
    if (status != 0) {
        bsphp_set_last_error("BCryptOpenAlgorithmProvider status=0x%08lX", (unsigned long)status);
        goto done;
    }
    status = BCryptImportKeyPair(hAlg, NULL, BCRYPT_RSAPRIVATE_BLOB, &hKey, blob, cbBlob, 0);
    if (status != 0) {
        bsphp_set_last_error("BCryptImportKeyPair status=0x%08lX (blob=%lu)", (unsigned long)status, (unsigned long)cbBlob);
        goto done;
    }
    BCryptCloseAlgorithmProvider(hAlg, 0);
    hAlg = NULL;

    status = BCryptDecrypt(hKey, (PUCHAR)cipher, (ULONG)cipher_len, NULL, NULL, 0, NULL, 0, &cb, BCRYPT_PAD_PKCS1);
    if (status != 0) {
        bsphp_set_last_error("BCryptDecrypt(size) status=0x%08lX", (unsigned long)status);
        goto done;
    }
    pt = (unsigned char *)malloc((size_t)cb + 1);
    if (!pt) goto done;
    status = BCryptDecrypt(hKey, (PUCHAR)cipher, (ULONG)cipher_len, NULL, NULL, 0, pt, cb, &cb, BCRYPT_PAD_PKCS1);
    if (status != 0) {
        bsphp_set_last_error("BCryptDecrypt(data) status=0x%08lX", (unsigned long)status);
        free(pt);
        pt = NULL;
        goto done;
    }
    pt[cb] = '\0';
    *out_plain = (char *)pt;
    pt = NULL;
    ok = 1;

done:
    bsphp_clear_rsa8(&n, &e, &d, &p, &q, &e1, &e2, &cf);
    if (blob) free(blob);
    if (hKey) BCryptDestroyKey(hKey);
    if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0);
    if (pt) free(pt);
    return ok;
}

int bsphp_rsa_decrypt_pkcs1_base64(const char *cipher_b64, const char *private_key_b64_or_pem, char **out_plain) {
    unsigned char *cipher = NULL;
    int cipher_len = 0;
    char *b64 = NULL;
    unsigned char *der = NULL;
    int der_len = 0;
    int ok = 0;

    g_bsphp_crypto_last_error[0] = '\0';
    if (!b64_decode_alloc_cipher(cipher_b64, &cipher, &cipher_len)) {
        bsphp_set_last_error("base64 decode cipher failed");
        goto done;
    }
    b64 = strip_pem_to_base64(private_key_b64_or_pem);
    if (!b64) {
        bsphp_set_last_error("strip pem failed");
        goto done;
    }
    if (!b64_decode_alloc(b64, &der, &der_len)) {
        bsphp_set_last_error("base64 decode private key failed");
        goto done;
    }

    if (!rsa_decrypt_bcrypt(cipher, cipher_len, der, der_len, out_plain)) goto done;
    ok = 1;

done:
    if (cipher) free(cipher);
    if (der) free(der);
    if (b64) free(b64);
    return ok;
}

char *bsphp_url_encode(const char *src) {
    size_t len = strlen(src);
    char *out = (char *)malloc(len * 3 + 1);
    char *p = out;
    if (!out) return NULL;
    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)src[i];
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            *p++ = (char)c;
        } else {
            sprintf(p, "%%%02X", c);
            p += 3;
        }
    }
    *p = '\0';
    return out;
}

static int hex_nibble(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
    if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
    return -1;
}

char *bsphp_url_decode(const char *src) {
    size_t len = strlen(src);
    char *out = (char *)malloc(len + 1);
    size_t j = 0;
    if (!out) return NULL;
    for (size_t i = 0; i < len;) {
        if (src[i] == '%' && i + 2 < len) {
            int hi = hex_nibble(src[i + 1]);
            int lo = hex_nibble(src[i + 2]);
            if (hi >= 0 && lo >= 0) {
                out[j++] = (char)((unsigned char)((hi << 4) | lo));
                i += 3;
                continue;
            }
        }
        /* 勿将 '+' 转为空格：响应体里 AES/RSA 的 Base64 含 '+' */
        out[j++] = src[i++];
    }
    out[j] = '\0';
    return out;
}

void bsphp_secure_free(char *ptr) {
    if (!ptr) return;
    size_t n = strlen(ptr);
    memset(ptr, 0, n);
    free(ptr);
}

