#include <iostream>
#include <string>
#include <sys/prctl.h>
#include <boost/asio.hpp>
#include "server/server.hpp"
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/formatter_parser.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/ssl/context.hpp>
#include <cstddef>
#include <memory>
#include <fstream>

#include "terminal_api_driver.h"


namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>


static std::vector<char> loadFile(const std::string& path) {
    std::ifstream is(path, std::ios::in | std::ios::binary);
    if (!is) {
        throw std::runtime_error("File not found");
    }

    std::vector<char> content;
    for (;;) {
        char buf[512];
        auto len = is.read(buf, sizeof(buf)).gcount();
        if (len <= 0) {
            break;
        }
        content.insert(content.end(), buf, buf + len);
    }
    return content;
}


namespace mime_types = http::server::mime_types;

void init_logging() {
    namespace log = boost::log;
    namespace keywords = log::keywords;
    namespace expressions =  log::expressions;
    namespace attributes =  log::attributes;

    log::register_simple_formatter_factory<log::trivial::severity_level, char>("Severity");

// #ifdef USE_DEBUG
//     log::add_console_log(std::clog, keywords::format = "%TimeStamp%: [%Severity%] %Message% [%ThreadID%]");
// #else
//     log::add_file_log(
//         keywords::file_name = "/home/root/manager_orlik_%N.log",
//         keywords::rotation_size = 10 * 1024 * 1024,
//         keywords::time_based_rotation = log::sinks::file::rotation_at_time_point(0, 0, 0),
//         keywords::format = expressions::format("%1% [%2%] [%3%] <%4%> [%5%]")
//           % expressions::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y-%m-%d, %H:%M:%S.%f")
//           % expressions::format_named_scope("Scope", keywords::format = "%n (%f:%l)")
//           % expressions::attr<log::trivial::severity_level>("Severity")
//           % expressions::message % expressions::attr<attributes::current_thread_id::value_type>("ThreadID"),
//         keywords::open_mode = std::ios_base::app,
//         keywords::auto_flush = true
//     );
// #endif
    log::add_console_log(std::clog, keywords::format = "%TimeStamp%: [%Severity%] %Message% [%ThreadID%]");
    log::core::get()->set_filter(log::trivial::severity >= log::trivial::info);

    log::add_common_attributes();
}

static void initResources(http::server::Server& s, std::shared_ptr<api_driver::ApiDriver>& api) {
    s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/", "static/main.html", mime_types::text_html));
    s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/login", "static/login.html", mime_types::text_html));

    s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/favicon.ico", "static/favicon.png", mime_types::image_png));
    s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/images/krokodil_vzryvaetsya_hd.gif", "static/krokodil.gif", mime_types::image_gif));

    s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/style.css", "static/style.css", mime_types::text_css));
    s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/fields.css", "static/fields.css", mime_types::text_css));
    s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/js/vue.js", "static/js/vue.js", mime_types::javascript));

    s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/statistics", [](const auto& req, auto& rep) {
        if (req.method != "GET") {
            http::server::stockReply(http::server::bad_request, rep);
        }

        rep.status = http::server::ok;
        rep.headers.clear();
        rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
        const char* json = R"({"key":"value"})";
        rep.content.insert(rep.content.end(), json, json + strlen(json));
    }));

    s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/get/statistics", [api](const auto& req, auto& rep) {
        if (req.method != "GET") {
            http::server::stockReply(http::server::bad_request, rep);
        }

        rep.status = http::server::ok;
        rep.headers.clear();
        rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
        std::string result = R"({"mainState":)";
        result += api->loadTerminalState();
        result += "}";
        rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
    }));

    s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/resetPacketStatistics", [api](const auto& req, auto& rep) {
        if (req.method != "POST") {
            http::server::stockReply(http::server::bad_request, rep);
        }
        api->resetPacketStatistics();

        rep.status = http::server::ok;
        rep.headers.clear();
        rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
        const std::string result = R"({"status":"ok")";
        rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
    }));

    s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/get/settings", [api](const auto& req, auto& rep) {
        if (req.method != "GET") {
            http::server::stockReply(http::server::bad_request, rep);
        }

        rep.status = http::server::ok;
        rep.headers.clear();
        rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
        std::string result = R"({"settings":)";
        result += api->loadSettings();
        result += "}";
        rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
    }));
}

int main(int argc, char *argv[]) {
    try {
        prctl(PR_SET_NAME, "main", 0, 0, 0);
        // Check command line arguments.
        if (argc != 4 && argc != 5) {
            std::cerr << "Usage: http_server <ssl|nossl> <address> <port> [static files directory]\n";
            std::cerr << "  For IPv4, try:\n";
            std::cerr << "    receiver nossl 0.0.0.0 80\n";
            std::cerr << "  For IPv6, try:\n";
            std::cerr << "    receiver nossl 0::0 80\n";
            return 1;
        }

        init_logging();
        boost::log::core::get()->add_thread_attribute("Scope", boost::log::attributes::named_scope());

#ifdef USE_DEBUG
        BOOST_LOG_TRIVIAL(info) << "Starting DEBUG " << argv[0];
#else
        BOOST_LOG_TRIVIAL(info) << "Starting RELEASE build" << argv[0];
#endif

        auto api = std::make_shared<api_driver::ApiDriver>();

        // Initialise the server.
        std::unique_ptr<http::server::Server> s;

        if (strcmp(argv[1], "nossl") == 0) {
            s = std::make_unique<http::server::Server>(argv[2], argv[3]);
            initResources(*s, api);
            s->run();

        } else if (strcmp(argv[1], "ssl") == 0) {
            const auto cert = loadFile("cert.pem");
            const auto key = loadFile("key.pem");
            const auto dh = loadFile("dh.pem");

            auto ctx = std::make_shared<ssl::context>(ssl::context::tlsv12);

            ctx->set_password_callback(
                [](std::size_t, ssl::context_base::password_purpose) {
                    return "test";
                });

            ctx->set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | ssl::context::single_dh_use);
            ctx->use_certificate_chain(boost::asio::buffer(cert));
            ctx->use_private_key(boost::asio::buffer(key), ssl::context::file_format::pem);
            ctx->use_tmp_dh(boost::asio::buffer(dh));

            s = std::make_unique<http::server::Server>(argv[2], argv[3], ctx);
            initResources(*s, api);
            s->run();
        } else {
            std::cerr << "Unsupported ssl mode: " << argv[1] << std::endl;
            return 1;
        }
    } catch (std::exception &e) {
        BOOST_LOG_TRIVIAL(error) << e.what() << std::endl;
        return -1;
    }

    return 0;
}


