#include "fileserver.hxx"
#include <corvusoft/restbed/session.hpp>
#include <corvusoft/restbed/resource.hpp>
#include <corvusoft/restbed/request.hpp>

using namespace mrpc;


inline std::string& operator<<(std::string &v, const rapidjson::Value &j) {
    if (!j.IsString())
        throw std::exception{};
    v = j.GetString();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::string &v, mrpc::MRPCJWriter &w) {
    w.String(v);
    return w;
}
inline std::int8_t& operator<<(std::int8_t &v, const rapidjson::Value &j) {
    if (!j.IsInt())
        throw std::exception{};
    v = j.GetInt();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::int8_t &v, mrpc::MRPCJWriter &w) {
    w.Int(v);
    return w;
}
inline std::int16_t& operator<<(std::int16_t &v, const rapidjson::Value &j) {
    if (!j.IsInt())
        throw std::exception{};
    v = j.GetInt();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::int16_t &v, mrpc::MRPCJWriter &w) {
    w.Int(v);
    return w;
}
inline std::int32_t& operator<<(std::int32_t &v, const rapidjson::Value &j) {
    if (!j.IsInt())
        throw std::exception{};
    v = j.GetInt();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::int32_t &v, mrpc::MRPCJWriter &w) {
    w.Int(v);
    return w;
}
inline std::int64_t& operator<<(std::int64_t &v, const rapidjson::Value &j) {
    if (!j.IsInt64())
        throw std::exception{};
    v = j.GetInt64();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::int64_t &v, mrpc::MRPCJWriter &w) {
    w.Int64(v);
    return w;
}
inline std::uint8_t& operator<<(std::uint8_t &v, const rapidjson::Value &j) {
    if (!j.IsUint())
        throw std::exception{};
    v = j.GetUint();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::uint8_t &v, mrpc::MRPCJWriter &w) {
    w.Uint(v);
    return w;
}
inline std::uint16_t& operator<<(std::uint16_t &v, const rapidjson::Value &j) {
    if (!j.IsUint())
        throw std::exception{};
    v = j.GetUint();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::uint16_t &v, mrpc::MRPCJWriter &w) {
    w.Uint(v);
    return w;
}
inline std::uint32_t& operator<<(std::uint32_t &v, const rapidjson::Value &j) {
    if (!j.IsUint())
        throw std::exception{};
    v = j.GetUint();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::uint32_t &v, mrpc::MRPCJWriter &w) {
    w.Uint(v);
    return w;
}
inline std::uint64_t& operator<<(std::uint64_t &v, const rapidjson::Value &j) {
    if (!j.IsUint64())
        throw std::exception{};
    v = j.GetUint64();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::uint64_t &v, mrpc::MRPCJWriter &w) {
    w.Uint64(v);
    return w;
}
inline bool& operator<<(bool &v, const rapidjson::Value &j) {
    if (!j.IsBool())
        throw std::exception{};
    v = j.GetBool();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const bool &v, mrpc::MRPCJWriter &w) {
    w.Bool(v);
    return w;
}
inline std::float_t& operator<<(std::float_t &v, const rapidjson::Value &j) {
    if (!j.IsDouble())
        throw std::exception{};
    v = j.GetDouble();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::float_t &v, mrpc::MRPCJWriter &w) {
    w.Double(v);
    return w;
}
inline std::double_t& operator<<(std::double_t &v, const rapidjson::Value &j) {
    if (!j.IsDouble())
        throw std::exception{};
    v = j.GetDouble();
    return v;
}
inline mrpc::MRPCJWriter& operator>>(const std::double_t &v, mrpc::MRPCJWriter &w) {
    w.Double(v);
    return w;
}


inline mrpc::MRPCJWriter& operator>>(const std::nullptr_t &, mrpc::MRPCJWriter &w) {
    w.Null();
    return w;
}

template<typename T>
inline std::vector<T>& operator<<(std::vector<T> &v, const rapidjson::Value &j);
template<typename T>
inline std::optional<T>& operator<<(std::optional<T> &v, const rapidjson::Value &j) {
    if (j.IsNull())
        v = std::nullopt;
    else {
        T t;
        t << j;
        v = std::move(t);
    }
    return v;
}

template<typename T>
inline std::vector<T>& operator<<(std::vector<T> &v, const rapidjson::Value &j) {
    if (!j.IsArray())
        throw std::exception{};
    for (const auto &e : j.GetArray()) {
        T t;
        t << e;
        v.push_back(std::move(t));
    }
    return v;
}

template<typename T>
inline mrpc::MRPCJWriter& operator>>(const std::vector<T> &v, mrpc::MRPCJWriter &w);
template<typename T>
inline mrpc::MRPCJWriter& operator>>(const std::optional<T> &v, mrpc::MRPCJWriter &w) {
    if (v.has_value())
        v.value() >> w;
    else
        w.Null();
    return w;
}

template<typename T>
inline mrpc::MRPCJWriter& operator>>(const std::vector<T> &v, mrpc::MRPCJWriter &w) {
    w.StartArray();
    for (const auto &e : v)
        e >> w;
    w.EndArray();
    return w;
}

inline const rapidjson::Value& json_get(const rapidjson::Value &j, const char *key) {
    auto member = j.FindMember(key);
    if (member == j.MemberEnd())
        throw std::exception{};
    return member->value;
}

namespace mrpc {

template<typename T>
MRPCJWriter& Response<T>::operator>>(MRPCJWriter &__w) const {
    __w.StartObject();
    __w.Key("e", 1);
    e >> __w;
    __w.Key("o", 1);
    o >> __w;
    __w.EndObject();
    return __w;
}
template<typename T>
Response<T>& Response<T>::operator<<(const rapidjson::Value &__j) {
    using namespace mrpc;
    e << json_get(__j, "e");
    o << json_get(__j, "o");
    return *this;
}

MRPCJWriter& LoginResponse::operator>>(MRPCJWriter &__w) const {
    __w.StartObject();
    __w.Key("otp_needed", 10);
    otp_needed >> __w;
    __w.Key("token", 5);
    token >> __w;
    __w.EndObject();
    return __w;
}
LoginResponse& LoginResponse::operator<<(const rapidjson::Value &__j) {
    using namespace mrpc;
    otp_needed << json_get(__j, "otp_needed");
    token << json_get(__j, "token");
    return *this;
}

MRPCJWriter& Session::operator>>(MRPCJWriter &__w) const {
    __w.StartObject();
    __w.Key("name", 4);
    name >> __w;
    __w.Key("tfa_enabled", 11);
    tfa_enabled >> __w;
    __w.Key("admin", 5);
    admin >> __w;
    __w.Key("sudo", 4);
    sudo >> __w;
    __w.EndObject();
    return __w;
}
Session& Session::operator<<(const rapidjson::Value &__j) {
    using namespace mrpc;
    name << json_get(__j, "name");
    tfa_enabled << json_get(__j, "tfa_enabled");
    admin << json_get(__j, "admin");
    sudo << json_get(__j, "sudo");
    return *this;
}

MRPCJWriter& UserInfo::operator>>(MRPCJWriter &__w) const {
    __w.StartObject();
    __w.Key("id", 2);
    id >> __w;
    __w.Key("name", 4);
    name >> __w;
    __w.Key("tfa", 3);
    tfa >> __w;
    __w.Key("admin", 5);
    admin >> __w;
    __w.Key("enabled", 7);
    enabled >> __w;
    __w.EndObject();
    return __w;
}
UserInfo& UserInfo::operator<<(const rapidjson::Value &__j) {
    using namespace mrpc;
    id << json_get(__j, "id");
    name << json_get(__j, "name");
    tfa << json_get(__j, "tfa");
    admin << json_get(__j, "admin");
    enabled << json_get(__j, "enabled");
    return *this;
}

MRPCJWriter& Node::operator>>(MRPCJWriter &__w) const {
    __w.StartObject();
    __w.Key("id", 2);
    id >> __w;
    __w.Key("name", 4);
    name >> __w;
    __w.Key("file", 4);
    file >> __w;
    __w.Key("preview", 7);
    preview >> __w;
    __w.Key("parent", 6);
    parent >> __w;
    __w.Key("size", 4);
    size >> __w;
    __w.Key("children", 8);
    children >> __w;
    __w.EndObject();
    return __w;
}
Node& Node::operator<<(const rapidjson::Value &__j) {
    using namespace mrpc;
    id << json_get(__j, "id");
    name << json_get(__j, "name");
    file << json_get(__j, "file");
    preview << json_get(__j, "preview");
    parent << json_get(__j, "parent");
    size << json_get(__j, "size");
    children << json_get(__j, "children");
    return *this;
}

MRPCJWriter& CreateNodeInfo::operator>>(MRPCJWriter &__w) const {
    __w.StartObject();
    __w.Key("id", 2);
    id >> __w;
    __w.Key("exists", 6);
    exists >> __w;
    __w.Key("file", 4);
    file >> __w;
    __w.EndObject();
    return __w;
}
CreateNodeInfo& CreateNodeInfo::operator<<(const rapidjson::Value &__j) {
    using namespace mrpc;
    id << json_get(__j, "id");
    exists << json_get(__j, "exists");
    file << json_get(__j, "file");
    return *this;
}

MRPCJWriter& ZipInfo::operator>>(MRPCJWriter &__w) const {
    __w.StartObject();
    __w.Key("done", 4);
    done >> __w;
    __w.Key("progress", 8);
    progress >> __w;
    __w.Key("total", 5);
    total >> __w;
    __w.EndObject();
    return __w;
}
ZipInfo& ZipInfo::operator<<(const rapidjson::Value &__j) {
    using namespace mrpc;
    done << json_get(__j, "done");
    progress << json_get(__j, "progress");
    total << json_get(__j, "total");
    return *this;
}

MRPCJWriter& PathSegment::operator>>(MRPCJWriter &__w) const {
    __w.StartObject();
    __w.Key("name", 4);
    name >> __w;
    __w.Key("id", 2);
    id >> __w;
    __w.EndObject();
    return __w;
}
PathSegment& PathSegment::operator<<(const rapidjson::Value &__j) {
    using namespace mrpc;
    name << json_get(__j, "name");
    id << json_get(__j, "id");
    return *this;
}


template<typename T>
void send_msg(const std::shared_ptr<restbed::Session> &c, const T &v) {
    if (c->is_closed())
        return;
    rapidjson::StringBuffer s;
    mrpc::MRPCJWriter writer{s};
    v >> writer;
    const auto body_ptr = s.GetString();
    const auto body = restbed::Bytes{body_ptr, body_ptr+s.GetLength()};
    c->yield(
        200,
        body,
        std::multimap<std::string, std::string>{
            {"Content-Type", "application/json"},
            {"Content-Length", std::to_string(body.size())}
        }
    );
}

template<typename T>
void send_sse_msg(const std::shared_ptr<restbed::Session> &c, const T &v) {
    if (c->is_closed())
        return;
    rapidjson::StringBuffer s;
    std::memcpy(s.Push(5), "data:", 5);
    mrpc::MRPCJWriter writer{s};
    v >> writer;
    std::memcpy(s.Push(2), "\n\n", 2);
    const auto body_ptr = s.GetString();
    const auto body = restbed::Bytes{body_ptr, body_ptr+s.GetLength()};
    c->yield(body);
}

mrpc::MRPCStreamImpl::MRPCStreamImpl(const std::shared_ptr<restbed::Session> &conn) : conn(conn) {
    conn->yield(
        200,
        std::multimap<std::string, std::string>{
		    {"Cache-Control", "no-cache"},
		    {"Content-Type", "text/event-stream"}
        }
    );
}

void mrpc::MRPCStreamImpl::close() const noexcept { conn->close("data:null\n\n"); }
bool mrpc::MRPCStreamImpl::is_open() const noexcept { return conn->is_open(); }
template<> void MRPCStream<std::string>::send(const std::string &v) const noexcept { send_sse_msg(conn, v); }


mrpc::MRPCServer::MRPCServer(std::shared_ptr<restbed::Resource> &r) {
    r->set_method_handler("POST", [this](const std::shared_ptr<restbed::Session>& s) {
        const auto req = s->get_request();
        const auto body_len = req->get_header("Content-Length", 0);
        s->fetch(body_len, [this](const std::shared_ptr<restbed::Session> &s, auto &&body) {
            try { msg_handler(s, body); }
            catch (const std::exception &_) { s->close(400); }
        });
    });
}

void mrpc::MRPCServer::msg_handler(const std::shared_ptr<restbed::Session> __c, const restbed::Bytes &__msg) {
    rapidjson::Document __j;
    __j.Parse((const char*)__msg.data(), __msg.size());
    if (__j.HasParseError())
        throw std::exception{};
    std::string __service, __method;
    __service << json_get(__j, "service");
    __method << json_get(__j, "method");
    auto __data_member = __j.FindMember("data");
    if (__data_member == __j.MemberEnd() || !__data_member->value.IsObject())
        throw std::exception{};
    auto &__data = __data_member->value;
    if (__service == "Auth") {
        if (__method == "signup") {
            
            std::string username; username << json_get(__data, "username");
                        std::string password; password << json_get(__data, "password");
            
            send_msg(__c, Auth_signup(std::move(username), std::move(password)));
        } else if (__method == "login") {
            
            std::string username; username << json_get(__data, "username");
                        std::string password; password << json_get(__data, "password");
                        std::optional<std::string> otp; otp << json_get(__data, "otp");
            
            send_msg(__c, Auth_login(std::move(username), std::move(password), std::move(otp)));
        } else if (__method == "send_recovery_key") {
            
            std::string username; username << json_get(__data, "username");
            
            Auth_send_recovery_key(std::move(username)); send_msg(__c, nullptr);
        } else if (__method == "reset_password") {
            
            std::string key; key << json_get(__data, "key");
                        std::string password; password << json_get(__data, "password");
            
            send_msg(__c, Auth_reset_password(std::move(key), std::move(password)));
        } else if (__method == "change_password") {
            
            std::string token; token << json_get(__data, "token");
                        std::string old_pw; old_pw << json_get(__data, "old_pw");
                        std::string new_pw; new_pw << json_get(__data, "new_pw");
            
            send_msg(__c, Auth_change_password(std::move(token), std::move(old_pw), std::move(new_pw)));
        } else if (__method == "logout") {
            
            std::string token; token << json_get(__data, "token");
            
            Auth_logout(std::move(token)); send_msg(__c, nullptr);
        } else if (__method == "logout_all") {
            
            std::string token; token << json_get(__data, "token");
            
            send_msg(__c, Auth_logout_all(std::move(token)));
        } else if (__method == "tfa_setup_mail") {
            
            std::string token; token << json_get(__data, "token");
            
            send_msg(__c, Auth_tfa_setup_mail(std::move(token)));
        } else if (__method == "tfa_setup_totp") {
            
            std::string token; token << json_get(__data, "token");
            
            send_msg(__c, Auth_tfa_setup_totp(std::move(token)));
        } else if (__method == "tfa_complete") {
            
            std::string token; token << json_get(__data, "token");
                        std::string otp; otp << json_get(__data, "otp");
            
            send_msg(__c, Auth_tfa_complete(std::move(token), std::move(otp)));
        } else if (__method == "tfa_disable") {
            
            std::string token; token << json_get(__data, "token");
            
            send_msg(__c, Auth_tfa_disable(std::move(token)));
        } else if (__method == "delete_user") {
            
            std::string token; token << json_get(__data, "token");
            
            send_msg(__c, Auth_delete_user(std::move(token)));
        } else if (__method == "session_info") {
            
            std::string token; token << json_get(__data, "token");
            
            send_msg(__c, Auth_session_info(std::move(token)));
        }
        else { throw std::exception{}; }
    } else if (__service == "Admin") {
        if (__method == "list_users") {
            
            std::string token; token << json_get(__data, "token");
            
            send_msg(__c, Admin_list_users(std::move(token)));
        } else if (__method == "delete_user") {
            
            std::string token; token << json_get(__data, "token");
                        std::uint64_t user; user << json_get(__data, "user");
            
            send_msg(__c, Admin_delete_user(std::move(token), std::move(user)));
        } else if (__method == "logout") {
            
            std::string token; token << json_get(__data, "token");
                        std::uint64_t user; user << json_get(__data, "user");
            
            send_msg(__c, Admin_logout(std::move(token), std::move(user)));
        } else if (__method == "disable_tfa") {
            
            std::string token; token << json_get(__data, "token");
                        std::uint64_t user; user << json_get(__data, "user");
            
            send_msg(__c, Admin_disable_tfa(std::move(token), std::move(user)));
        } else if (__method == "set_admin") {
            
            std::string token; token << json_get(__data, "token");
                        std::uint64_t user; user << json_get(__data, "user");
                        bool admin; admin << json_get(__data, "admin");
            
            send_msg(__c, Admin_set_admin(std::move(token), std::move(user), std::move(admin)));
        } else if (__method == "set_enabled") {
            
            std::string token; token << json_get(__data, "token");
                        std::uint64_t user; user << json_get(__data, "user");
                        bool enabled; enabled << json_get(__data, "enabled");
            
            send_msg(__c, Admin_set_enabled(std::move(token), std::move(user), std::move(enabled)));
        } else if (__method == "sudo") {
            
            std::string token; token << json_get(__data, "token");
                        std::uint64_t user; user << json_get(__data, "user");
            
            send_msg(__c, Admin_sudo(std::move(token), std::move(user)));
        } else if (__method == "unsudo") {
            
            std::string token; token << json_get(__data, "token");
            
            send_msg(__c, Admin_unsudo(std::move(token)));
        } else if (__method == "shutdown") {
            
            std::string token; token << json_get(__data, "token");
            
            send_msg(__c, Admin_shutdown(std::move(token)));
        }
        else { throw std::exception{}; }
    } else if (__service == "FS") {
        if (__method == "get_node") {
            
            std::string token; token << json_get(__data, "token");
                        std::uint64_t node; node << json_get(__data, "node");
            
            send_msg(__c, FS_get_node(std::move(token), std::move(node)));
        } else if (__method == "get_path") {
            
            std::string token; token << json_get(__data, "token");
                        std::uint64_t node; node << json_get(__data, "node");
            
            send_msg(__c, FS_get_path(std::move(token), std::move(node)));
        } else if (__method == "get_nodes_size") {
            
            std::string token; token << json_get(__data, "token");
                        std::vector<std::uint64_t> nodes; nodes << json_get(__data, "nodes");
            
            send_msg(__c, FS_get_nodes_size(std::move(token), std::move(nodes)));
        } else if (__method == "create_node") {
            
            std::string token; token << json_get(__data, "token");
                        bool file; file << json_get(__data, "file");
                        std::uint64_t parent; parent << json_get(__data, "parent");
                        std::string name; name << json_get(__data, "name");
            
            send_msg(__c, FS_create_node(std::move(token), std::move(file), std::move(parent), std::move(name)));
        } else if (__method == "move_nodes") {
            
            std::string token; token << json_get(__data, "token");
                        std::vector<std::uint64_t> nodes; nodes << json_get(__data, "nodes");
                        std::uint64_t parent; parent << json_get(__data, "parent");
            
            send_msg(__c, FS_move_nodes(std::move(token), std::move(nodes), std::move(parent)));
        } else if (__method == "delete_nodes") {
            auto __stream = MRPCStream<std::string>{__c};
            
            std::string token; token << json_get(__data, "token");
                        std::vector<std::uint64_t> nodes; nodes << json_get(__data, "nodes");
            
            FS_delete_nodes(std::move(token), std::move(nodes), std::move(__stream));
        } else if (__method == "download_preview") {
            
            std::string token; token << json_get(__data, "token");
                        std::uint64_t node; node << json_get(__data, "node");
            
            send_msg(__c, FS_download_preview(std::move(token), std::move(node)));
        } else if (__method == "get_mime") {
            
            std::string token; token << json_get(__data, "token");
                        std::uint64_t node; node << json_get(__data, "node");
            
            send_msg(__c, FS_get_mime(std::move(token), std::move(node)));
        }
        else { throw std::exception{}; }
    }
    else { throw std::exception{}; }
}
}
