#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/store.h>
#include <openssl/crypto.h>
#include <openssl/ui.h>
#include <openssl/ecdsa.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>

#include "mlogger.h"
#include "openssl_utils.h"

void free_X509Certs_list(x509_list_t *chain)
{
    if (chain)
    {
        for (int i=0; i<chain->count; i++)
        {
            X509_free(chain->items[i]);
            chain->items[i]=NULL;
        }
        chain->count = 0;
    }
}

void append_X509Certs_fromFile(x509_list_t *chain, const char* filename)
{
    if (filename != NULL) {
        OSSL_STORE_CTX* store = OSSL_STORE_open(filename, UI_null(), NULL, NULL, NULL);
        if (store != NULL) {
            while (OSSL_STORE_eof(store) != 1) {
                OSSL_STORE_INFO* info = OSSL_STORE_load(store);
                if (info != NULL) {
                    if (OSSL_STORE_error(store) == 1) {
                        printe("OSSL_STORE_load");
                    } else {
                        const int type = OSSL_STORE_INFO_get_type(info);

                        if (type == OSSL_STORE_INFO_CERT) {
                            // get a copy of the certificate
                            struct x509_st* cert = OSSL_STORE_INFO_get1_CERT(info);
                            chain->items[chain->count++] = cert;
                        }
                    }
                }
                OSSL_STORE_INFO_free(info);
            }
        }
        OSSL_STORE_close(store);
    }
}

void append_X509SubCert_fromString(x509_list_t *chain, const uint8_t* subStream, int length)
{
    if (chain)
    {
        struct x509_st* subCert = d2i_X509(NULL, &subStream, length);
        if (subCert == NULL)
        {
            printe("d2i_X509 failed.");
        }else{
            chain->items[chain->count++] = subCert;
            printi("d2i_X509 success.");
        }
    }
}

struct x509_st* load_X509Cert_fromString(const uint8_t* stream, int length)
{
    struct x509_st* cert = d2i_X509(NULL, &stream, length);
    if (cert == NULL) {
        printe("d2i_X509 failed.");
    } else {
        printi("d2i_X509 success.");
    }
    return cert;
}

int verify_X509Certs_withV2GMO(struct x509_st* cert, x509_list_t* subCerts, const char* root_cert_path_v2g, const char* root_cert_path_mo)
{
    int err = X509_V_ERR_UNSPECIFIED;
    if (cert == NULL) goto return_err;
    X509* target = X509_dup(cert);
    if (target == NULL) goto free_target;
    STACK_OF(X509)* chain = sk_X509_new_null();
    if (chain == NULL) goto free_chain;
    X509_STORE* roots = X509_STORE_new();
    if (roots == NULL) goto free_roots;
    X509_STORE_CTX* store = X509_STORE_CTX_new();
    if (store == NULL) goto free_store;

    x509_list_t trusted_chain = {.items = {NULL}, .count=0};
    append_X509Certs_fromFile(&trusted_chain, root_cert_path_v2g);
    append_X509Certs_fromFile(&trusted_chain, root_cert_path_mo);
    for (int i=0; i< trusted_chain.count; i++) 
    {
        if (X509_STORE_add_cert(roots, trusted_chain.items[i]) != 1) 
        {
            printe("X509_STORE_add_cert");
        }
    }

    for (int i=0; i< subCerts->count; i++) 
    {
        if (X509_add_cert(chain, subCerts->items[i], X509_ADD_FLAG_UP_REF | X509_ADD_FLAG_NO_DUP | X509_ADD_FLAG_NO_SS) != 1) 
        {
            printe("X509_add_cert");
        }
    }

    if (X509_STORE_CTX_init(store, roots, target, chain) == 1) {
        if (X509_STORE_CTX_verify(store) == 1) {
            err = X509_STORE_CTX_get_error(store);
            if (err != X509_V_OK) {
                printe("X509_STORE_CTX_verify(%d) : %s\n", X509_STORE_CTX_get_error_depth(store), X509_verify_cert_error_string(err));
            }
        }
    }

    free_X509Certs_list(&trusted_chain);

free_store:
    X509_STORE_CTX_free(store);
free_roots:
    X509_STORE_free(roots);
free_chain:
    sk_X509_pop_free(chain, X509_free);
free_target:
    X509_free(target);
return_err:
    return err;
}

const uint8_t *get_EMAID_fromX509Cert(struct x509_st* cert)
{
    // DO NOT FREE - internal pointers to certificate
    const X509_NAME* subject = X509_get_subject_name(cert);
    if (subject != NULL) {
        for (int i = 0; i < X509_NAME_entry_count(subject); i++) {
            const X509_NAME_ENTRY* name_entry = X509_NAME_get_entry(subject, i);
            if (name_entry != NULL) {
                const ASN1_OBJECT* object = X509_NAME_ENTRY_get_object(name_entry);
                const ASN1_STRING* data = X509_NAME_ENTRY_get_data(name_entry);
                if ((object != NULL) && (data != NULL)) {
                    const uint8_t* name = OBJ_nid2sn(OBJ_obj2nid(object));
                    const uint8_t* value = ASN1_STRING_get0_data(data);
                    if (strncmp(name, "CN", 2)==0)
                    {
                        printi("X509_get_subject_name(CN) success.");
                        return value;
                    }
                }
            }
        }
    }
    printe("X509_get_subject_name(CN) failed.");
    return NULL;
}

EVP_PKEY* get_PublicKey_fromX509Cert(struct x509_st* cert)
{
    EVP_PKEY* pkey = X509_get_pubkey(cert);
    if (pkey == NULL)
    {
        printe("X509_get_pubkey failed.");
    }else{
        printi("X509_get_pubkey success.");
    }
    return pkey;
}

int verify_Signature_UsePKEY(EVP_PKEY* pkey, const uint8_t* r, const uint8_t* s, const uint8_t* digest)
{
    int ret = 0;
    ECDSA_SIG* signature = ECDSA_SIG_new();
    if (signature)
    {
        BIGNUM* rbn = BN_bin2bn(r, 32, NULL);
        if (rbn)
        {
            BIGNUM*sbn = BN_bin2bn(s, 32, NULL);
            if (sbn)
            {
                if (ECDSA_SIG_set0(signature, rbn, sbn) == 1)
                {
                    uint8_t *sig_p;
                    int sig_p_len = i2d_ECDSA_SIG(signature, &sig_p);
                    if (sig_p_len > 0)
                    {
                        EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL);
                        if (ctx) 
                        {
                            if (EVP_PKEY_verify_init(ctx) == 1)
                            {
                                if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) == 1)
                                {
                                    if (EVP_PKEY_verify(ctx, sig_p, sig_p_len, digest, strlen(digest)) == 1)
                                    {
                                        ret = 1;
                                    }
                                }
                            }
                            EVP_PKEY_CTX_free(ctx);
                        }
                        OPENSSL_free(sig_p);
                    }
                }
                BN_free(sbn);
            }
            BN_free(rbn);
        }
        ECDSA_SIG_free(signature);
    } 
    return ret;
}