#include "mnemonic.h"

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "esp_random.h"

#include "mbedtls/md.h"
#include "mbedtls/pkcs5.h"
#include "mbedtls/sha256.h"

#include "mnemonic_wordlist.h"

#define MNEMONIC_12_WORDS 12
#define ENTROPY_12_BYTES 16
#define CHECKSUM_12_BITS 4
#define TOTAL_12_BITS (ENTROPY_12_BYTES * 8 + CHECKSUM_12_BITS)

static bool s_letter_index_built = false;
static size_t s_letter_start[26] = {0};
static size_t s_letter_count[26] = {0};

static bool append_word(char *out, size_t out_len, size_t *cursor, const char *word)
{
    const size_t word_len = strlen(word);

    if (*cursor >= out_len) {
        return false;
    }

    if (*cursor != 0) {
        if (*cursor + 1 >= out_len) {
            return false;
        }
        out[*cursor] = ' ';
        (*cursor)++;
    }

    if (*cursor + word_len >= out_len) {
        return false;
    }

    memcpy(out + *cursor, word, word_len);
    *cursor += word_len;
    out[*cursor] = '\0';
    return true;
}

static uint8_t get_bit_from_entropy_checksum(const uint8_t entropy[ENTROPY_12_BYTES],
                                             const uint8_t checksum[32],
                                             size_t bit_index)
{
    if (bit_index < ENTROPY_12_BYTES * 8) {
        const size_t byte_index = bit_index / 8;
        const size_t bit_in_byte = 7 - (bit_index % 8);
        return (uint8_t)((entropy[byte_index] >> bit_in_byte) & 0x01);
    }

    const size_t checksum_bit_index = bit_index - (ENTROPY_12_BYTES * 8);
    const size_t bit_in_byte = 7 - checksum_bit_index;
    return (uint8_t)((checksum[0] >> bit_in_byte) & 0x01);
}

static int bip39_word_index(const char *word)
{
    if (word == NULL || word[0] == '\0') {
        return -1;
    }

    for (size_t i = 0; i < 2048; ++i) {
        if (strcmp(word, BIP39_WORDLIST[i]) == 0) {
            return (int)i;
        }
    }

    return -1;
}

static bool next_token(const char **cursor, char *out_word, size_t out_len)
{
    size_t n = 0;
    const char *p = *cursor;

    while (*p != '\0' && isspace((unsigned char)*p)) {
        ++p;
    }

    if (*p == '\0') {
        *cursor = p;
        return false;
    }

    while (*p != '\0' && !isspace((unsigned char)*p)) {
        if (n + 1 >= out_len) {
            return false;
        }
        out_word[n++] = *p++;
    }

    out_word[n] = '\0';
    *cursor = p;
    return true;
}

static void build_letter_index(void)
{
    if (s_letter_index_built) {
        return;
    }

    for (size_t i = 0; i < 26; ++i) {
        s_letter_start[i] = 0;
        s_letter_count[i] = 0;
    }

    for (size_t i = 0; i < 2048; ++i) {
        char c = BIP39_WORDLIST[i][0];
        if (c < 'a' || c > 'z') {
            continue;
        }

        size_t li = (size_t)(c - 'a');
        if (s_letter_count[li] == 0) {
            s_letter_start[li] = i;
        }
        s_letter_count[li]++;
    }

    s_letter_index_built = true;
}

int generate_mnemonic_12(char *out, size_t out_len)
{
    uint8_t entropy[ENTROPY_12_BYTES] = {0};
    uint8_t hash[32] = {0};
    size_t cursor = 0;

    if (out == NULL || out_len == 0) {
        return -1;
    }

    out[0] = '\0';

    esp_fill_random(entropy, sizeof(entropy));
    mbedtls_sha256(entropy, sizeof(entropy), hash, 0);

    for (size_t w = 0; w < MNEMONIC_12_WORDS; ++w) {
        const size_t start_bit = w * 11;
        uint16_t index = 0;

        for (size_t b = 0; b < 11; ++b) {
            index = (uint16_t)((index << 1) |
                               get_bit_from_entropy_checksum(entropy, hash, start_bit + b));
        }

        if (index >= 2048) {
            memset(entropy, 0, sizeof(entropy));
            memset(hash, 0, sizeof(hash));
            return -1;
        }

        if (!append_word(out, out_len, &cursor, BIP39_WORDLIST[index])) {
            memset(entropy, 0, sizeof(entropy));
            memset(hash, 0, sizeof(hash));
            out[0] = '\0';
            return -1;
        }
    }

    if (TOTAL_12_BITS != 132) {
        memset(entropy, 0, sizeof(entropy));
        memset(hash, 0, sizeof(hash));
        out[0] = '\0';
        return -1;
    }

    memset(entropy, 0, sizeof(entropy));
    memset(hash, 0, sizeof(hash));
    return 0;
}

int mnemonic_validate(const char *mnemonic)
{
    uint16_t word_indices[MNEMONIC_12_WORDS] = {0};
    uint8_t entropy[ENTROPY_12_BYTES] = {0};
    uint8_t hash[32] = {0};
    const char *cursor = mnemonic;
    char token[16];
    size_t words = 0;
    size_t bit_pos = 0;

    if (mnemonic == NULL) {
        return -1;
    }

    while (next_token(&cursor, token, sizeof(token))) {
        int idx;

        if (words >= MNEMONIC_12_WORDS) {
            memset(entropy, 0, sizeof(entropy));
            memset(hash, 0, sizeof(hash));
            return -1;
        }

        idx = bip39_word_index(token);
        if (idx < 0) {
            memset(entropy, 0, sizeof(entropy));
            memset(hash, 0, sizeof(hash));
            return -1;
        }

        word_indices[words++] = (uint16_t)idx;
    }

    if (words != MNEMONIC_12_WORDS) {
        memset(entropy, 0, sizeof(entropy));
        memset(hash, 0, sizeof(hash));
        return -1;
    }

    for (size_t w = 0; w < MNEMONIC_12_WORDS; ++w) {
        for (int b = 10; b >= 0; --b) {
            uint8_t bit = (uint8_t)((word_indices[w] >> b) & 0x01);
            if (bit_pos < ENTROPY_12_BYTES * 8) {
                size_t byte_index = bit_pos / 8;
                size_t bit_in_byte = 7 - (bit_pos % 8);
                entropy[byte_index] |= (uint8_t)(bit << bit_in_byte);
            }
            bit_pos++;
        }
    }

    mbedtls_sha256(entropy, sizeof(entropy), hash, 0);

    for (size_t i = 0; i < CHECKSUM_12_BITS; ++i) {
        size_t checksum_bit_pos = ENTROPY_12_BYTES * 8 + i;
        size_t word_idx = checksum_bit_pos / 11;
        size_t bit_in_word = 10 - (checksum_bit_pos % 11);
        uint8_t expected = (uint8_t)((hash[0] >> (7 - i)) & 0x01);
        uint8_t actual = (uint8_t)((word_indices[word_idx] >> bit_in_word) & 0x01);
        if (actual != expected) {
            memset(entropy, 0, sizeof(entropy));
            memset(hash, 0, sizeof(hash));
            return -1;
        }
    }

    memset(entropy, 0, sizeof(entropy));
    memset(hash, 0, sizeof(hash));
    return 0;
}

size_t mnemonic_words_for_letter(char letter, size_t *out_start)
{
    int idx;

    if (out_start == NULL) {
        return 0;
    }

    if (letter >= 'A' && letter <= 'Z') {
        letter = (char)(letter - 'A' + 'a');
    }

    if (letter < 'a' || letter > 'z') {
        *out_start = 0;
        return 0;
    }

    build_letter_index();

    idx = letter - 'a';
    *out_start = s_letter_start[idx];
    return s_letter_count[idx];
}

size_t mnemonic_words_with_prefix(const char *prefix, uint16_t *out_indices, size_t max_out)
{
    size_t start = 0;
    size_t count = 0;
    size_t written = 0;

    if (prefix == NULL || prefix[0] == '\0') {
        return 0;
    }

    count = mnemonic_words_for_letter(prefix[0], &start);
    if (count == 0) {
        return 0;
    }

    for (size_t i = 0; i < count; ++i) {
        size_t idx = start + i;
        const char *word = BIP39_WORDLIST[idx];
        if (strncmp(word, prefix, strlen(prefix)) == 0) {
            if (out_indices != NULL && written < max_out) {
                out_indices[written] = (uint16_t)idx;
            }
            written++;
        }
    }

    return written;
}

bool mnemonic_letter_extends_prefix(const char *prefix, size_t prefix_len, char next_letter)
{
    char probe[16];
    size_t start = 0;
    size_t count = 0;

    if (next_letter >= 'A' && next_letter <= 'Z') {
        next_letter = (char)(next_letter - 'A' + 'a');
    }

    if (next_letter < 'a' || next_letter > 'z') {
        return false;
    }

    if (prefix == NULL || prefix_len >= sizeof(probe) - 1) {
        return false;
    }

    memcpy(probe, prefix, prefix_len);
    probe[prefix_len] = next_letter;
    probe[prefix_len + 1] = '\0';

    count = mnemonic_words_for_letter(probe[0], &start);
    if (count == 0) {
        return false;
    }

    for (size_t i = 0; i < count; ++i) {
        const char *word = BIP39_WORDLIST[start + i];
        if (strncmp(word, probe, prefix_len + 1) == 0) {
            return true;
        }
    }

    return false;
}

const char *mnemonic_word_at(size_t index)
{
    if (index >= 2048) {
        return NULL;
    }
    return BIP39_WORDLIST[index];
}

int mnemonic_to_seed(const char *mnemonic, uint8_t seed[64])
{
    const mbedtls_md_info_t *md_info;
    mbedtls_md_context_t ctx;
    uint8_t salt[8] = {'m', 'n', 'e', 'm', 'o', 'n', 'i', 'c'};

    if (mnemonic == NULL || seed == NULL) {
        return -1;
    }

    md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
    if (md_info == NULL) {
        return -1;
    }

    mbedtls_md_init(&ctx);
    if (mbedtls_md_setup(&ctx, md_info, 1) != 0) {
        mbedtls_md_free(&ctx);
        return -1;
    }

    (void)ctx;
    if (mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA512,
                                      (const uint8_t *)mnemonic,
                                      strlen(mnemonic),
                                      salt,
                                      sizeof(salt),
                                      2048,
                                      64,
                                      seed) != 0) {
        mbedtls_md_free(&ctx);
        return -1;
    }

    mbedtls_md_free(&ctx);
    return 0;
}
