#include "server_internal.hxx"

std::shared_ptr<Token> Server::get_token(const std::string &token) {
    std::shared_lock lock{token_lock};
    const auto &entry = tokens.find(token);
    if (entry == tokens.end())
        return nullptr;
    return entry->second;
}

std::shared_ptr<User> Server::get_user(std::uint64_t id) {
    std::shared_lock lock{user_lock};
    const auto &entry = users.find(id);
    if (entry == users.end())
        return nullptr;
    return entry->second;
}

std::shared_ptr<User> Server::is_token_valid(const std::string &token) {
    auto t = get_token(token);
    if (!t)
        return nullptr;
    if (Token::clock::now() <= t->expire) {
        t->refresh();
        return t->user;
    }
    {
        std::unique_lock lock{token_lock};
        tokens.erase(token);
    }
    return nullptr;
}

void Server::logout_user(std::uint64_t id) {
    std::unique_lock lock{token_lock};
    for (auto it = tokens.begin(); it != tokens.end();) {
        if (it->second->user->id == id)
            tokens.erase(it++);
        else
            ++it;
    }
}

void Server::delete_user(const std::shared_ptr<User> &user) {
    std::unique_lock lock{user_lock};
    logout_user(user->id);
    delete_node(user, 0, [](const std::string&){});
    users.erase(user->id);
}

void Server::send_tfa_mail(const std::shared_ptr<User> &user) {
    std::lock_guard lock{mail_otp_lock};
    std::string code; code.reserve(10);
    for (int i = 0; i < 10; ++i) {
        auto j = auth_rng->next_byte();
        while (j > 249) j = auth_rng->next_byte();
        code.push_back('0' + (j%10));
    }
    mail_otp.emplace(code, std::make_pair(user->id, Token::clock::now() + std::chrono::minutes{10}));
    send_mail(user->name, "MFileserver - TFA code", "Your code is: " + code + "\r\nIt is valid for 10 minutes");
}

bool Server::check_mail_code(const std::shared_ptr<User> &user, const std::string &code) {
    std::lock_guard lock{mail_otp_lock};
    auto now = Token::clock::now();
    for (auto it = mail_otp.begin(); it != mail_otp.end();) {
        if (now >= it->second.second)
            mail_otp.erase(it++);
        else
            ++it;
    }
    const auto &entry = mail_otp.find(code);
    bool ok = entry != mail_otp.end() && entry->second.first == user->id;
    if (ok)
        mail_otp.erase(code);
    return ok;
}

bool Server::check_tfa_code(const std::shared_ptr<User> &user, const std::string &code_str) {
    Botan::OctetString secret{user->tfa_secret};
    Botan::TOTP totp{secret};
    try {
        std::uint32_t code = std::stoul(code_str);
        return totp.verify_totp(code, std::chrono::system_clock::now(), 1);
    } catch (std::exception &_) {}
    return false;
}
