#include "bot.hpp"
#include "challenge.hpp"
#include "client.hpp"
#include "connection.hpp"
#include "openssl_utils.hpp"
#include "registration.hpp"
#include "settings.hpp"
#include "irc_coroutine.hpp"

#include <boost/asio.hpp>
#include <boost/log/trivial.hpp>

#include <openssl/pem.h>

#include <fstream>
#include <iostream>
#include <memory>

using namespace std::literals;


static auto start(boost::asio::io_context &io, const Settings &settings) -> void
{
    X509_Ref cert;
    if (settings.use_tls && not settings.tls_certfile.empty())
    {
        cert = cert_from_file(settings.tls_certfile);
    }

    EVP_PKEY_Ref key;
    if (settings.use_tls && not settings.tls_keyfile.empty())
    {
        key = key_from_file(settings.tls_keyfile, "");
    }

    const auto connection = std::make_shared<Connection>(io);
    const auto client = Client::start(*connection);
    Registration::start(settings, client);

    const auto bot = Bot::start(client);

    if (not settings.challenge_username.empty() && not settings.challenge_key_file.empty()) {
        if (auto key = key_from_file(settings.challenge_key_file, settings.challenge_key_password)) {
            client->sig_registered.connect([&settings, connection, key = std::move(key)]() {
                Challenge::start(*connection, settings.challenge_username, key);
            });
        }
    }

    connection->sig_disconnect.connect(
        [&io, &settings, client, bot]() {
            client->shutdown();
            bot->shutdown();

            auto timer = std::make_shared<boost::asio::steady_timer>(io);
            timer->expires_after(5s);
            timer->async_wait([&io, &settings, timer](auto) { start(io, settings); });
        }
    );

    bot->sig_command.connect([connection](const Command &cmd) {
        std::cout << "COMMAND " << cmd.command << " from " << cmd.account << std::endl;
    });

    connection->start({
        .tls = settings.use_tls,
        .host = settings.host,
        .port = settings.service,
        .verify = settings.tls_hostname,
        .client_cert = std::move(cert),
        .client_key = std::move(key),
    });
}

static auto get_settings(const char *filename) -> Settings
{
    if (auto config_stream = std::ifstream{filename})
    {
        return Settings::from_stream(config_stream);
    }
    else
    {
        BOOST_LOG_TRIVIAL(error) << "Unable to open configuration";
        std::exit(1);
    }
}

auto main(int argc, char *argv[]) -> int
{
    if (argc != 2) {
        BOOST_LOG_TRIVIAL(error) << "Bad arguments";
        return 1;
    }
    const auto settings = get_settings(argv[1]);
    auto io = boost::asio::io_context{};
    start(io, settings);
    io.run();
}