#include "data_internal.hxx"

template<typename... Args>
inline void ok(bool ok, spdlog::format_string_t<Args...> fmt, Args &&...args) {
    if (!ok) {
        data_logger->critical("Data validation failed:");
        data_logger->critical(fmt, std::forward<Args>(args)...);
        crash();
    }
}

void validate_node(const std::string &username, const std::filesystem::path &user_dir, Node *node) {
    auto name = "User " + username + " node " + std::to_string(node->id) + ":" + node->name;
    if (node->id != 0) {
        ok(!node->name.empty(), "{} is missing name", name);
        ok(node->parent != nullptr, "{} is missing parent", name);
    }

    if (node->file) {
        auto file = user_dir / std::to_string(node->id);
        ok(std::filesystem::exists(file), "{} is missing file on disk", name);
        auto size = std::filesystem::file_size(file);
        ok(node->size == size, "{} size does not match (Node,Disk): {} != {}", name, node->size, size);
        if (node->preview)
            ok(std::filesystem::exists(file.replace_extension("png")), "{} is missing preview on disk", name);
    }
}

void validate_user(User *user) {
    auto name = std::to_string(user->id) + ":" + user->name;

    ok(!user->name.empty(), "User {} is missing name", user->id);
    ok(!user->password.empty(), "User {} is missing password", name);
    ok(user->nodes.contains(0), "User {} is missing root", name);
    if (user->tfa_enabled && !user->tfa_mail)
        ok(!user->tfa_secret.empty(), "User {} is missing tfa secret", name);

    ok(!user->user_dir.empty(), "User {} user_dir is not set", name);
    ok(std::filesystem::exists(user->user_dir), "User {} is missing files directory", name);

    std::uint64_t last_node_id = 0;
    for (const auto &node : user->nodes) {
        validate_node(name, user->user_dir, node.second.get());
        last_node_id = std::max(last_node_id, node.first);
    }

    ok(user->next_node_id > last_node_id, "User {}: Next node id {} must be larger than the last used id {}", name, user->next_node_id, last_node_id);
}

void Data::validate() {
    data_logger->info("Validating data");

    auto start = std::chrono::high_resolution_clock::now();
    try {
        std::uint64_t last_user_id = 0;
        for (const auto &user: users) {
            validate_user(user.second.get());
            last_user_id = std::max(last_user_id, user.first);
        }

        if (!users.empty())
            ok(next_user_id > last_user_id, "Next user id {} must be larger than the last used id {}", next_user_id, last_user_id);
    } catch (std::exception &e) {
        data_logger->critical("Data validation failed with error: {}", e.what());
        crash();
    }
    auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start);
    data_logger->info("Data validation took {} ms", dur.count());

    data_logger->info("Data validated");
}
