#define RAPIDJSON_HAS_STDSTRING 1
#include <rapidjson/filereadstream.h>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include "data_internal.hxx"

#define FIND_MEMBER(name, ty) m = doc.FindMember(#name); \
    if (m == doc.MemberEnd() || !m->value.Is##ty()) { data_logger->error("Missing or invalid "#name); throw std::exception{}; }
#define ASSIGN_MEMBER(lhs, name, ty) FIND_MEMBER(name, ty) lhs = m->value.Get##ty()

SaveNode load_node(const rapidjson::Value &doc) {
    rapidjson::Value::ConstMemberIterator m;
    auto node = std::make_unique<Node>();
    std::uint64_t id, parent;

    node->parent = nullptr;

    ASSIGN_MEMBER(node->id, id, Uint64);
    id = node->id;
    data_logger->debug("Loading node {}", id);

    ASSIGN_MEMBER(node->name, name, String);
    ASSIGN_MEMBER(node->file, file, Bool);
    ASSIGN_MEMBER(node->preview, preview, Bool);
    ASSIGN_MEMBER(node->size, size, Uint64);

    ASSIGN_MEMBER(parent, parent, Uint64);

    std::list<std::uint64_t> children;
    FIND_MEMBER(children, Array)
    for (const auto &v : m->value.GetArray())
        if (!v.IsUint64()) { data_logger->error("Invalid child id"); throw std::exception{}; }
        else children.push_back(v.GetUint64());

    return {
        .node = std::move(node),
        .id = id,
        .parent_id = parent,
        .children_ids = children
    };
}

std::unique_ptr<User> load_user(const rapidjson::Value &doc) {
    rapidjson::Value::ConstMemberIterator m;
    auto user = std::make_unique<User>();

    ASSIGN_MEMBER(user->id, id, Uint64);
    data_logger->debug("Loading user {}", user->id);

    ASSIGN_MEMBER(user->name, name, String);
    ASSIGN_MEMBER(user->password, password, String);
    ASSIGN_MEMBER(user->tfa_secret, tfa_secret, String);
    ASSIGN_MEMBER(user->enabled, enabled, Bool);
    ASSIGN_MEMBER(user->admin, admin, Bool);
    ASSIGN_MEMBER(user->tfa_enabled, tfa_enabled, Bool);
    ASSIGN_MEMBER(user->tfa_mail, tfa_mail, Bool);
    ASSIGN_MEMBER(user->next_node_id, next_node_id, Uint64);

    std::list<SaveNode> nodes;
    FIND_MEMBER(nodes, Array)
    for (const auto &v : m->value.GetArray())
        nodes.push_back(load_node(v));

    for (auto &node : nodes)
        user->nodes.emplace(node.id, node.node.release());

    for (const auto &snode : nodes) {
        auto &node = user->nodes.at(snode.id);
        if (node->id != 0)
            node->parent = user->nodes.at(snode.parent_id);
        for (const auto &child : snode.children_ids)
            node->children.push_back(user->nodes.at(child));
    }

    user->user_dir = files_dir / std::to_string(user->id);

    return user;
}

bool load_from_file(Data &data, const std::string &file) {
    char buf[65536];
    bool success = false;
    data_logger->info("Loading data from {}", file);
    data.users.clear();
    auto start = std::chrono::high_resolution_clock::now();
    try {
        FileWrapper f{file.c_str(), "r"};
        rapidjson::FileReadStream fstream{f.f, buf, sizeof(buf)};
        rapidjson::Document doc;
        rapidjson::Document::MemberIterator m;
        doc.ParseStream(fstream);
        if (doc.HasParseError()) {
            data_logger->error("Failed to parse data");
            auto e = rapidjson::GetParseError_En(doc.GetParseError());
            data_logger->error("{}", e);
            throw std::exception{};
        }

        ASSIGN_MEMBER(data.version, version, Uint64);
        if (data.version != Data::current_version) {
            data_logger->error("Version is {}, current version is {}. Refusing to load!", data.version, Data::current_version);
            throw std::exception{};
        }

        ASSIGN_MEMBER(data.next_user_id, next_user_id, Uint64);

        FIND_MEMBER(users, Array)
        for (const auto &v : m->value.GetArray()) {
            auto user = load_user(v);
            data.users.emplace(user->id, std::move(user));
        }

        success = true;
    } catch (std::exception &_) {}
    auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start);
    data_logger->info("Data loading took {} ms", dur.count());
    return success;
}

void Data::load() {
    if (std::filesystem::exists(data_cur_file)) {
        bool ok = load_from_file(*this, data_cur_file);
        if (!ok) {
            data_logger->warn("Retrying from old file");
            if (!std::filesystem::exists(data_old_file)) {
                data_logger->critical("Old file missing");
                crash();
            }
            ok = load_from_file(*this, data_old_file);
            if (!ok) {
                data_logger->critical("Failed to load data");
                crash();
            }
        }
    } else if (std::filesystem::exists(data_old_file)) {
        data_logger->warn("Data file missing, loading from old file");
        bool ok = load_from_file(*this, data_old_file);
        if (!ok) {
            data_logger->critical("Failed to load data");
            crash();
        }
    } else data_logger->info("No data file found for loading");
}
