#include "driver-ssl.hpp"
#include <iostream>
#include <cassert>
#include <vector>
#include <filesystem>

extern std::string strerror_str(int err);

namespace drvssl {

const char *dummy_cert = 
    "-----BEGIN CERTIFICATE-----\n"
    "MIIDezCCAmOgAwIBAgIUajKmxrLMr9zBMlphrTJU5qKG8FgwDQYJKoZIhvcNAQEL"
    "BQAwTDELMAkGA1UEBhMCVVMxFTATBgNVBAgMDFBlbm5zeWx2YW5pYTESMBAGA1UE"
    "CgwJbG9jYWxob3N0MRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMjIwMzIyMTczMzA4"
    "WhgPMjEyMjAyMjYxNzMzMDhaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQIDAxQZW5u"
    "c3lsdmFuaWExEjAQBgNVBAoMCWxvY2FsaG9zdDESMBAGA1UEAwwJbG9jYWxob3N0"
    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5OWIaKqYae4nPxvu5EP3"
    "VilcjApYcMT4+2ypfQoB6PEep5lwguA929rNsTKnhGsEiQAZ0eZPEZN7VhUwf/hz"
    "26jIyTT43ELkt6k97wwSZSXuT65RpSiemwEs6g2mMwzpgP6nv+yam4HjE9AKiHGN"
    "YeTV72Nw1EN70t6IjIf4jsJRXqDJkUx5sSSD6j0WBTOhzozIDgZHTDwiLhatE66m"
    "SNoD8oWC0PscbUgOJkFpbaCAS8RJmpsdgkTFae2rzL9cOFLGw6OgV/BV1J1s0ks8"
    "+veoMMtIO6fese+OZ+DyQbuGaoaltZUXzY6QjD5l34m2mGplelT7BrpcqJTBHwmh"
    "CwIDAQABo1MwUTAdBgNVHQ4EFgQUXQM5TVfJ9gpUXg8fZ8yfuUVcBP8wHwYDVR0j"
    "BBgwFoAUXQM5TVfJ9gpUXg8fZ8yfuUVcBP8wDwYDVR0TAQH/BAUwAwEB/zANBgkq"
    "hkiG9w0BAQsFAAOCAQEAqYX/ZGv0Qh/xdXppjnqojm8mH0giDW4tvwMqHcW3YRa3"
    "9J2yYot+rHjU5g4n6HEmWDBE0eqLz9n3Y3fkFzT8RWZwBaST965CgsfGofyuA2hC"
    "Ddn4Am3B5tTPmi8WWRZg8amhpGVD/mwkoVFIK0M337b1aZUJYPE+Kc9WetSL2KqB"
    "EhqSQpkAWhVadzP85dq2T9EDjAvhlFTFlDEBx1GDUcc8M0KQ9NEvLT7LgoUcbMiT"
    "PerlSZQTB0crchXTRSERgiwu80r7D6STn/RcPL9Fg5PkA94/d87jGbmV4sxSRsvM"
    "z+DnJGjHrV1J/jHPrnVvVLpigBlGno3C5O/sRw3gcQ=="
    "-----END CERTIFICATE-----\n";

const char *dummy_key =
    "-----BEGIN PRIVATE KEY-----\n"
    "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDk5Yhoqphp7ic/"
    "G+7kQ/dWKVyMClhwxPj7bKl9CgHo8R6nmXCC4D3b2s2xMqeEawSJABnR5k8Rk3tW"
    "FTB/+HPbqMjJNPjcQuS3qT3vDBJlJe5PrlGlKJ6bASzqDaYzDOmA/qe/7JqbgeMT"
    "0AqIcY1h5NXvY3DUQ3vS3oiMh/iOwlFeoMmRTHmxJIPqPRYFM6HOjMgOBkdMPCIu"
    "Fq0TrqZI2gPyhYLQ+xxtSA4mQWltoIBLxEmamx2CRMVp7avMv1w4UsbDo6BX8FXU"
    "nWzSSzz696gwy0g7p96x745n4PJBu4ZqhqW1lRfNjpCMPmXfibaYamV6VPsGulyo"
    "lMEfCaELAgMBAAECggEBAJa1AiFX4U4tva1xqNKmZV1XklWqIhzts7lnDBkF08gZ"
    "qcNT5Z5mIpR09eVropwvEidZ56Yp63l5D0XYYbyAS1gfQ0QnGot7h7fdOKgB3MK4"
    "PLY94gfKPNN17KqWHg2SvNNv1+cn04v78xUCb0zy5tHDp5Acexdm70ohtupARElJ"
    "LSHdS7ebsqZUFXbbM3BpPEsQLi3PrzNs1DrKkZ3rR6eMGrsDqExXx8/foi9aZKsd"
    "BGM2/kcTJ5aY6NhSv5iqO1oK46sbMrjVW/bYNsOyl0eFjwTRahn+Zhp/JMewZYeu"
    "715g6kzbZNwEzBLgrhNPF6E2ycEr/C6z5bE78g5QCkECgYEA8s07UUY25bjYiWWy"
    "W38pT7d/OXBSyKnq16N6MjVahl29r7nezFiDeLhLC0QiwXu/+qyxVZkB95MMGZXS"
    "AsaKFNis3AJ6eR4SYyhpSScYKNvlKIiW37TtR4FDcy7y5LL6tFpiDDIGH3LuyWNo"
    "d76142MBpv5aStnLGYU3pcZj43sCgYEA8VbNM4nqgSCQcbnHYjvsgphEMNSaoVie"
    "xob2uigXdV6Te0ayoUFBnVNKVsRhk+sswuTV4k1pK/On+USVl2tQ16tcaVMjTfSD"
    "HLYTJLmt6s4DcywWj5dfkbDoe5PulGXNZE960qXmOC62Lf0VMRwJ5x4FBRvGTjKC"
    "zvekI2/kO7ECgYEAhBGeclb/BXXGUvY+TgadMf9d9KBkZ0IFu8Xwcd8TnoLe6vbv"
    "ebery75zE228egIWKwREcYsIxuH1cvVLhrb35N73J7UxaTAyUD1rB598RL1XqPSj"
    "HIwNhReK2NxwwnWYaQHA02FiczjRKjooWPojdcwk2fEArDZLg1YzLrj7HIECgYEA"
    "htdx1Y8ESFtyeShMv5UtoxYCW6oeL3H9XH0CE6bc3IYYLvOkULbOO2HTEkGtJ2Fp"
    "5AbJfiS0U4tS2dI5Jp4eUDH9cxexjRfFvd/5ODbKdnver5X9kQMJsbQ/YPSZg66R"
    "oK9Lt7Bbvh5TScSy93psCgba1SzckspkDdGNkwMsaTECgYEAnFWaxormLUpXQRLs"
    "tKzMMHgVnHlsHiqXH432zmT2fpGZHYoWbsGuQjjrHGnSiu3QbDhnzM6y/T2GRs6z"
    "zHteIo/tzIyxg4MvJGJ9qANA7HoiKBdQ7G/I/NLJIyWAjj+e7/hgzKFcf+dpjpDq"
    "HcKc9a4WXhC7yu79e5BnKWltHXY="
    "-----END PRIVATE KEY-----\n";

std::string errors_string(bool lastonly) {
    std::string err;
    const char *file, *data, *func;
    int line, flags;

    while (true) {
        unsigned long code =
            ERR_get_error_all(&file, &line, &func, &data, &flags);
        if (code == 0) break;
        std::string reason;
        if (ERR_SYSTEM_ERROR(code)) {
            reason = strerror_str(ERR_GET_REASON(code));
        } else {
            const char *rc = ERR_reason_error_string(code);
            reason = (rc == nullptr) ? "unknown" : rc;
        }
        if (err.empty() || lastonly) {
            err = reason;
        } else {
            err = err + ", " + reason;
        }
        if (data != nullptr) {
            err = err + " " + data;
        }
    }
    return err;
}

void assert_errors_empty() {
    int code = ERR_peek_error();
    if (code != 0) {
        std::cerr << "SSL should not have errors at this point." << std::endl;
        ERR_print_errors_fp(stderr);
        exit(1);
    }
}

SSL_CTX *new_context(int verify) {
    SSL_CTX *ctx = SSL_CTX_new(TLS_method());
    SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
    SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
    SSL_CTX_set_verify(ctx, verify, nullptr);
    return ctx;
}

static int ctx_use_certificate_str(SSL_CTX *ctx, const char *str) {
    UniqueBIO bio(BIO_new(BIO_s_mem()));
    BIO_puts(bio.get(), str);
    UniqueX509 certificate(PEM_read_bio_X509(bio.get(), NULL, NULL, NULL));
    return SSL_CTX_use_certificate(ctx, certificate.get());
}

static int ctx_use_privatekey_str(SSL_CTX *ctx, const char *str) {
    UniqueBIO bio(BIO_new(BIO_s_mem()));
    BIO_puts(bio.get(), str);
    UniquePKEY pkey(PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, NULL));
    return SSL_CTX_use_PrivateKey(ctx, pkey.get());
}

void ctx_load_dummy_cert(SSL_CTX *ctx) {
    ERR_clear_error();
    if (ctx_use_certificate_str(ctx, dummy_cert) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    if (ctx_use_privatekey_str(ctx, dummy_key) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(1);
    }
}

static int count_certificates(const char *fn) {
    static char null_passwd;
    ErrClearErrorOnExit ece;
    UniqueBIO bio(BIO_new(BIO_s_file()));
    assert(bio != nullptr);
    if (BIO_read_filename(bio.get(), fn) <= 0) {
        std::cerr << "Cannot open file: " << fn << std::endl;
        exit(1);
    }
    int total = 0;
    while (true) {
        UniqueX509 x(PEM_read_bio_X509_AUX(bio.get(), nullptr, nullptr, &null_passwd));
        if (x == nullptr) break;
        total += 1;
    }
    return total;
}

static bool contains_privatekey(const char *fn) {
    static char null_passwd;
    ErrClearErrorOnExit ece;
    UniqueBIO bio(BIO_new(BIO_s_file()));
    assert(bio != nullptr);
    if (BIO_read_filename(bio.get(), fn) <= 0) {
        std::cerr << "Cannot open file: " << fn << std::endl;
        exit(1);
    }
    UniquePKEY k(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, &null_passwd));
    return k != nullptr;
}
    
void ctx_load_cert_from_directory(SSL_CTX *ctx, const std::string &dir) {
    std::vector<std::string> key_paths;
    std::vector<std::string> cert_paths;

    for (const auto & entry : std::filesystem::directory_iterator(dir)) {
        std::string fn = entry.path();
        if (count_certificates(fn.c_str()) >= 1) {
            cert_paths.push_back(fn);
        }
        if (contains_privatekey(fn.c_str())) {
            key_paths.push_back(fn);
        }
    }

    if (cert_paths.size() > 1) {
        std::cerr << "Directory contains multiple certs: " << dir << std::endl;
        exit(1);
    }
    if (key_paths.size() > 1) {
        std::cerr << "Directory contains multiple keys: " << dir << std::endl;
        exit(1);
    }
    if (cert_paths.empty()) {
        std::cerr << "Directory doesn't contain a cert: " << dir << std::endl;
        exit(1);
    }
    if (key_paths.empty()) {
        std::cerr << "Directory doesn't contain a key: " << dir << std::endl;
        exit(1);
    }

    int status;
    status = SSL_CTX_use_PrivateKey_file(ctx, key_paths[0].c_str(), SSL_FILETYPE_PEM);
    assert(status == 1);
    status = SSL_CTX_use_certificate_chain_file(ctx, cert_paths[0].c_str());
    assert(status == 1);
}

} // namespace drvssl

