#include "test.h"
#include <memory>
#include <openssl/err.h>
#include <openssl/dh.h>
#include <openssl/ssl.h>
#include <openssl/conf.h>
#include <openssl/engine.h>
#include <catch2/reporters/catch_reporter_registrars.hpp>
#include <catch2/reporters/catch_reporter_event_listener.hpp>

Variation variation;
string root_vdir = "tests/var";

struct VariationReseter : Catch::EventListenerBase {
    using EventListenerBase::EventListenerBase; // inherit constructor

    void testCaseStarting(const Catch::TestCaseInfo&) override {
        variation = {};
    }

    void testRunEnded(Catch::TestRunStats const&) override {
        if (Fs::exists(root_vdir)) {
            Fs::remove_all(root_vdir);
        }
    };
};
CATCH_REGISTER_LISTENER(VariationReseter);

SslContext get_ssl_ctx() {
    static SslContext ctx = nullptr;
    if (ctx) {
        return ctx;
    }
    auto raw = SSL_CTX_new(SSLv23_server_method());
    if (!raw) throw Error(make_ssl_error_code(SSL_ERROR_SSL));
    ctx = SslContext::attach(raw);

    SSL_CTX_use_certificate_file(ctx, "tests/cert/ca.pem", SSL_FILETYPE_PEM);
    SSL_CTX_use_PrivateKey_file(ctx, "tests/cert/ca.key", SSL_FILETYPE_PEM);
    if (!SSL_CTX_check_private_key(ctx)) throw panda::exception("bad ctx");
    return ctx;
}

TcpSP make_basic_server (const LoopSP& loop, const SockAddr& sa) {
    TcpSP server = new Tcp(loop);
    server->bind(sa);
    server->listen(1);
    return server;
}

TcpSP make_ssl_server (const LoopSP& loop, const SockAddr& sa) {
    auto server = make_basic_server(loop, sa);
    server->use_ssl(get_ssl_ctx());
    return server;
}

TcpSP make_server (const LoopSP& loop, const SockAddr& sa) {
    TcpSP server = new Tcp(loop);
    server->bind(sa);
    if (variation.ssl) server->use_ssl(get_ssl_ctx());
    server->listen(10000);
    return server;
}

TcpSP make_client (const LoopSP& loop) {
    TcpSP client = new Tcp(loop, AF_INET);

    if (variation.ssl) client->use_ssl();

    if (variation.buf) {
        client->recv_buffer_size(1);
        client->send_buffer_size(1);
    }

    return client;
}

TcpP2P make_tcp_pair (const LoopSP& loop, const SockAddr& sa) {
    TcpP2P ret;
    ret.server = make_server(loop, sa);
    ret.client = make_client(loop);
    ret.client->connect(ret.server->sockaddr().value());
    return ret;
}

TcpP2P make_p2p (const LoopSP& loop, const SockAddr& sa) {
    TcpP2P ret = make_tcp_pair(loop, sa);
    ret.server->connection_event.add([&](auto, auto sconn, auto& err) {
        if (err) throw err;
        ret.sconn = panda::dynamic_pointer_cast<Tcp>(sconn);
        loop->stop();
    });
    loop->run();
    assert(ret.sconn);
    return ret;
}