#include <balancer/client/experimental/requesters/balancer_client/balancer_client.h>
#include <balancer/client/experimental/util/requester_utils.h>
#include <balancer/client/experimental/util/request_utils.h>

#include "util/server.h"

#include <library/cpp/testing/gtest/gtest.h>

#include <util/generic/string.h>
#include <util/system/types.h>

#include <openssl/bio.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/pem.h>

#include <memory>

#define RSA_KEY_LENGTH 2048

using namespace NHttp;

bool GenerateX509(const TString& certFileName, const TString& keyFileName, long daysValid)
{
    bool result = false;

    std::unique_ptr<BIO, void (*)(BIO *)> certFile  { BIO_new_file(certFileName.data(), "wb"), BIO_free_all  };
    std::unique_ptr<BIO, void (*)(BIO *)> keyFile { BIO_new_file(keyFileName.data(), "wb"), BIO_free_all };

    if (certFile && keyFile)
    {
        std::unique_ptr<RSA, void (*)(RSA *)> rsa { RSA_new(), RSA_free };
        std::unique_ptr<BIGNUM, void (*)(BIGNUM *)> bn { BN_new(), BN_free };

        BN_set_word(bn.get(), RSA_F4);
        int rsa_ok = RSA_generate_key_ex(rsa.get(), RSA_KEY_LENGTH, bn.get(), nullptr);

        if (rsa_ok == 1)
        {
            // --- cert generation ---
            std::unique_ptr<X509, void (*)(X509 *)> cert { X509_new(), X509_free };
            std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)> pkey { EVP_PKEY_new(), EVP_PKEY_free};

            // The RSA structure will be automatically freed when the EVP_PKEY structure is freed.
            EVP_PKEY_assign(pkey.get(), EVP_PKEY_RSA, reinterpret_cast<char*>(rsa.release()));
            ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), 1); // serial number

            X509_gmtime_adj(X509_get_notBefore(cert.get()), 0); // now
            X509_gmtime_adj(X509_get_notAfter(cert.get()), daysValid * 24 * 3600); // accepts secs

            X509_set_pubkey(cert.get(), pkey.get());

//             1 -- X509_NAME may disambig with wincrypt.h
//             2 -- DO NO FREE the name internal pointer
//            X509_name_st* name = X509_get_subject_name(cert.get());

//            const ui8 country[] = "RU";
//            const ui8 company[] = "Yandex";
//            const ui8 common_name[] = "yandex.ru";
//
//            X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC, country, -1, -1, 0);
//            X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC, company, -1, -1, 0);
//            X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, common_name, -1, -1, 0);

//            X509_set_issuer_name(cert.get(), name);
            X509_sign(cert.get(), pkey.get(), EVP_sha256()); // some hash type here


            int ret  = PEM_write_bio_PrivateKey(keyFile.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
            int ret2 = PEM_write_bio_X509(certFile.get(), cert.get());

            result = (ret == 1) && (ret2 == 1); // OpenSSL return codes
        }
    }

    return result;
}

TEST(Http, Https) {
    GenerateX509("./cert.pem", "./key.pem", 1);

    THttpServer::TOptions serverOptions;
    serverOptions.CertPath = "./cert.pem";
    serverOptions.KeyPath = "./key.pem";
    serverOptions.Handler = [](NBalancerServer::THttpRequestEnv& request) -> NSrvKernel::TError {
        NSrvKernel::TResponse response;
        response.ResponseLine() = NSrvKernel::TResponseLine{200, "OK"};
        request.GetReplyTransport()->SendHead(std::move(response));
        request.GetReplyTransport()->SendEof();
        return {};
    };
    THttpServer server{serverOptions};

    NRequesters::TBalancerClient::TOptions options;
    options.CertPath = "./cert.pem";
    NRequesters::TBalancerClient client{options};

    TRequest request = NUtils::BuildRequest("https://127.0.0.1:" + ToString(server.GetSslPort())).Success();

    TFullResponse response = NUtils::Send(client, request, TInstant::Max()).Success();
    ASSERT_EQ(response.Head.StatusCode, HTTP_OK);
}
