#include <gtk/gtk.h>

#include <validate.h>
#include <paint.h>

#define DATA_KILL_APP 1

extern GtkApplication *app;
int dont_kill_the_app_pls = 0;

/**
 * Way too complicated for this challenge, but I promised some good security
 */

#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/bio.h>

const char* public_key = 
"-----BEGIN PUBLIC KEY-----\n"
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL9v4j6aGhikcIUQdNUq4Qj9hkLvRpvC\n"
"3jXMmnivS1niM9ws3f7ZhaKqQyUUCb0HPWWal5FQYAhvj27A9wLcH3kCAwEAAQ==\n"
"-----END PUBLIC KEY-----";

RSA* create_public_rsa(const char* key) {
    RSA *rsa = NULL;
    BIO *keybio;
    
    keybio = BIO_new_mem_buf(key, -1);
    
    if (keybio==NULL) 
    {
        return NULL;
    }

    return PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL);
}

void base64_decode(const char* msg, unsigned char** buffer, size_t* length) {
    BIO *bio, *b64;

    size_t len = strlen(msg);
    int padding = 0;  
    if (msg[len-1] == '=' && msg[len-2] == '=') // last two chars are =
        padding = 2;
    else if (msg[len-1] == '=') // last char is =
        padding = 1;
  
    size_t decode_len = (len * 3) / 4 - padding;


    *buffer = (uint8_t*) malloc(decode_len + 1);
    (*buffer)[decode_len] = '\0';

    EVP_DecodeBlock(*buffer, (unsigned char*) msg, strlen(msg));
    *length = decode_len;
}

int verify_signature(const char* public_key, const char* msg, const char* sig_base64)
{
    uint8_t* signature; // = (uint8_t*) malloc(64);
    size_t signature_len;

    RSA* public_rsa = create_public_rsa(public_key);
    base64_decode(sig_base64, &signature, &signature_len);
    
    EVP_PKEY* pubkey = EVP_PKEY_new();
    EVP_PKEY_assign_RSA(pubkey, public_rsa);
    EVP_MD_CTX* ctx = EVP_MD_CTX_create();

    if (EVP_DigestVerifyInit(ctx, NULL, EVP_sha256(), NULL, pubkey) <= 0) 
    {
        return 0;
    }

    if (EVP_DigestVerifyUpdate(ctx, msg, strlen(msg)) <= 0) 
    {
        return 0;
    }

    int auth_status = EVP_DigestVerifyFinal(ctx, signature, signature_len);
    EVP_MD_CTX_destroy(ctx);
    return auth_status;
}

/**
 * -------------
 */

/**
 * Format is now
 * 
 * SHTP-<8-letter-random-string>-<signed-signature>
 */
int check_is_key_valid(const char* key)
{
    int ret = 0;

    /* Duplicate the key so we can edit it */
    char* dup = strdup(key);

    /* Not the correct format */
    if (dup[4] != '-' || dup[13] != '-') goto cleanup;

    dup[4] = '\0';
    dup[13] = '\0';

    /* Starts with SHTP */
    if (strcmp(dup, "SHTP")) goto cleanup;

    ret = verify_signature(public_key, &dup[5], &dup[14]);

cleanup:
    free(dup);
    return ret;
}

static void invalid_key(GtkWindow* window)
{
    GtkWidget* dialog = gtk_message_dialog_new(window, GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Your entered key is not valid");

    g_signal_connect_swapped(GTK_WINDOW(dialog), "response", G_CALLBACK(gtk_widget_destroy), dialog);
    
    gtk_widget_show_all(dialog);
    gtk_widget_show_all(GTK_WIDGET(window));
}

static void save_key(const char* key)
{
    FILE* f = fopen(KEYSTORE_FILE, "w+");
    fprintf(f, key);
    fclose(f);
}

G_MODULE_EXPORT void button_validate_clicked(GtkWidget* widget, gpointer data)
{
    GtkWidget* window = gtk_widget_get_toplevel(widget);
    GtkEntryBuffer* buffer = gtk_entry_get_buffer(GTK_ENTRY(data));
    const char* key = gtk_entry_buffer_get_text(buffer);

    if (!check_is_key_valid(key))
    {
        invalid_key(GTK_WINDOW(window));
        return;
    }

    /* The key was valid, save the key for later and start the program */
    save_key(key);

    /* Destroy the old window, but make sure the application doesn't exit by overriding the destroy event */
    dont_kill_the_app_pls = 1;
    gtk_window_close(GTK_WINDOW(window));

    /* start the actual paint app */
    build_paint();
}

static void close_application(GtkWidget* widget, gpointer data)
{
    /* Ugly, hacky, but it works */
    if (!dont_kill_the_app_pls)
        g_application_quit(G_APPLICATION(app));
}

extern const char* validate_xml;

void build_license_key_popup()
{
    GtkBuilder* builder = gtk_builder_new_from_string(validate_xml, strlen(validate_xml));
    gtk_builder_set_application(builder, app);
    gtk_builder_connect_signals(builder, NULL);
    GtkWidget* window = GTK_WIDGET(gtk_builder_get_object(builder, "validate_window"));
    g_signal_connect(window, "destroy", G_CALLBACK(close_application), NULL);
    gtk_widget_show_all(window);
}