// SPDX-License-Identifier: GPL-2.0-only
/*
 *  ppm/src/core/crypto.c
 *
 *  Copyright (C) 2026 Project Tick
 */

#include <ppm/crypto.h>

#include <sodium.h>
#include <stdlib.h>
#include <string.h>

struct ppm_sha512_ctx {
    crypto_hash_sha512_state state;
};

ppm_error_t ppm_crypto_init(void) {
    static int initialised = 0;
    if (initialised) return PPM_OK;
    if (sodium_init() < 0) return PPM_ERR_INTERNAL;
    initialised = 1;
    return PPM_OK;
}

ppm_error_t ppm_sha512_buf(const void *data, size_t len, ppm_sha512_t *out) {
    if (!data && len) return PPM_ERR_INVALID;
    if (!out)         return PPM_ERR_INVALID;
    if (crypto_hash_sha512(out->bytes, data, len) != 0) return PPM_ERR_INTERNAL;
    return PPM_OK;
}

ppm_error_t ppm_sha512_init(ppm_sha512_ctx_t **out) {
    if (!out) return PPM_ERR_INVALID;
    ppm_sha512_ctx_t *ctx = malloc(sizeof(*ctx));
    if (!ctx) return PPM_ERR_NOMEM;
    if (crypto_hash_sha512_init(&ctx->state) != 0) {
        free(ctx);
        return PPM_ERR_INTERNAL;
    }
    *out = ctx;
    return PPM_OK;
}

ppm_error_t ppm_sha512_update(ppm_sha512_ctx_t *ctx, const void *data, size_t len) {
    if (!ctx) return PPM_ERR_INVALID;
    if (!data && len) return PPM_ERR_INVALID;
    if (crypto_hash_sha512_update(&ctx->state, data, len) != 0) return PPM_ERR_INTERNAL;
    return PPM_OK;
}

ppm_error_t ppm_sha512_final(ppm_sha512_ctx_t *ctx, ppm_sha512_t *out) {
    if (!ctx || !out) return PPM_ERR_INVALID;
    if (crypto_hash_sha512_final(&ctx->state, out->bytes) != 0) return PPM_ERR_INTERNAL;
    return PPM_OK;
}

void ppm_sha512_free(ppm_sha512_ctx_t *ctx) {
    if (!ctx) return;
    sodium_memzero(&ctx->state, sizeof(ctx->state));
    free(ctx);
}

ppm_error_t ppm_sha512_stream(FILE *stream, ppm_sha512_t *out) {
    if (!stream || !out) return PPM_ERR_INVALID;

    ppm_sha512_ctx_t *ctx = NULL;
    ppm_error_t err = ppm_sha512_init(&ctx);
    if (err) return err;

    unsigned char buf[64 * 1024];
    size_t n;
    while ((n = fread(buf, 1, sizeof(buf), stream)) > 0) {
        err = ppm_sha512_update(ctx, buf, n);
        if (err) { ppm_sha512_free(ctx); return err; }
    }
    if (ferror(stream)) { ppm_sha512_free(ctx); return PPM_ERR_IO; }

    err = ppm_sha512_final(ctx, out);
    ppm_sha512_free(ctx);
    return err;
}

ppm_error_t ppm_sha512_file(const char *path, ppm_sha512_t *out) {
    if (!path || !out) return PPM_ERR_INVALID;
    FILE *f = fopen(path, "rb");
    if (!f) return PPM_ERR_IO;
    ppm_error_t err = ppm_sha512_stream(f, out);
    fclose(f);
    return err;
}

void ppm_sha512_to_hex(const ppm_sha512_t *in, char *out) {
    sodium_bin2hex(out, PPM_SHA512_HEX_BUFSZ, in->bytes, PPM_SHA512_BYTES);
}

ppm_error_t ppm_sha512_from_hex(const char *hex, ppm_sha512_t *out) {
    if (!hex || !out) return PPM_ERR_INVALID;
    size_t bin_len = 0;
    if (sodium_hex2bin(out->bytes, PPM_SHA512_BYTES,
                       hex, strlen(hex),
                       NULL, &bin_len, NULL) != 0) {
        return PPM_ERR_PARSE;
    }
    if (bin_len != PPM_SHA512_BYTES) return PPM_ERR_PARSE;
    return PPM_OK;
}

bool ppm_sha512_equal(const ppm_sha512_t *a, const ppm_sha512_t *b) {
    if (!a || !b) return false;
    return sodium_memcmp(a->bytes, b->bytes, PPM_SHA512_BYTES) == 0;
}

ppm_error_t ppm_ed25519_verify(const uint8_t pk[PPM_ED25519_PK_BYTES],
                               const uint8_t sig[PPM_ED25519_SIG_BYTES],
                               const void *msg, size_t msg_len) {
    if (!pk || !sig || (!msg && msg_len)) return PPM_ERR_INVALID;
    if (crypto_sign_ed25519_verify_detached(sig, msg, msg_len, pk) != 0) {
        return PPM_ERR_SIG_INVALID;
    }
    return PPM_OK;
}

/* Decode a fixed-width hex blob, ignoring surrounding whitespace/newlines. */
static ppm_error_t ed25519_from_hex(const char *hex, uint8_t *out, size_t want) {
    if (!hex || !out) return PPM_ERR_INVALID;
    size_t bin_len = 0;
    if (sodium_hex2bin(out, want, hex, strlen(hex),
                       " \t\r\n", &bin_len, NULL) != 0) {
        return PPM_ERR_PARSE;
    }
    if (bin_len != want) return PPM_ERR_PARSE;
    return PPM_OK;
}

ppm_error_t ppm_ed25519_pk_from_hex(const char *hex,
                                    uint8_t out[PPM_ED25519_PK_BYTES]) {
    return ed25519_from_hex(hex, out, PPM_ED25519_PK_BYTES);
}

ppm_error_t ppm_ed25519_sig_from_hex(const char *hex,
                                     uint8_t out[PPM_ED25519_SIG_BYTES]) {
    return ed25519_from_hex(hex, out, PPM_ED25519_SIG_BYTES);
}
