#include "store.hpp"

using namespace std::literals::string_literals;

// hopefully the casting between signed and unsigned int64 doesnt cause issues

Store::Store(bool mem_store) {
    if (mem_store) {
        m_db_path = ":memory:";
        m_db_err = sqlite3_open(":memory:", &m_db);
    } else {
        m_db_path = std::filesystem::temp_directory_path() / "abaddon-store.db";
        m_db_err = sqlite3_open(m_db_path.string().c_str(), &m_db);
    }

    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "error opening database: %s\n", sqlite3_errstr(m_db_err));
        return;
    }

    // clang-format off
    m_db_err = sqlite3_exec(m_db, R"(
        PRAGMA writable_schema = 1;
        DELETE FROM sqlite_master;
        PRAGMA writable_schema = 0;
        VACUUM;
        PRAGMA integrity_check;
    )", nullptr, nullptr, nullptr);
    // clang-format on
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to clear database: %s\n", sqlite3_errstr(m_db_err));
        return;
    }

    m_db_err = sqlite3_exec(m_db, "PRAGMA journal_mode = WAL", nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "enabling write-ahead-log failed: %s\n", sqlite3_errstr(m_db_err));
        return;
    }

    m_db_err = sqlite3_exec(m_db, "PRAGMA synchronous = NORMAL", nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "setting synchronous failed: %s\n", sqlite3_errstr(m_db_err));
        return;
    }

    CreateTables();
    CreateStatements();
}

Store::~Store() {
    Cleanup();

    m_db_err = sqlite3_close(m_db);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "error closing database: %s\n", sqlite3_errstr(m_db_err));
        return;
    }

    if (m_db_path != ":memory:") {
        std::error_code ec;
        std::filesystem::remove(m_db_path, ec);
    }
}

bool Store::IsValid() const {
    return m_db_err == SQLITE_OK;
}

void Store::SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban) {
    Bind(m_set_ban_stmt, 1, guild_id);
    Bind(m_set_ban_stmt, 2, user_id);
    Bind(m_set_ban_stmt, 3, ban.Reason);

    if (!RunInsert(m_set_ban_stmt))
        fprintf(stderr, "ban insert failed: %s\n", sqlite3_errstr(m_db_err));
}

void Store::SetChannel(Snowflake id, const ChannelData &chan) {
    Bind(m_set_chan_stmt, 1, id);
    Bind(m_set_chan_stmt, 2, static_cast<int>(chan.Type));
    Bind(m_set_chan_stmt, 3, chan.GuildID);
    Bind(m_set_chan_stmt, 4, chan.Position);
    Bind(m_set_chan_stmt, 5, nullptr); // unused
    Bind(m_set_chan_stmt, 6, chan.Name);
    Bind(m_set_chan_stmt, 7, chan.Topic);
    Bind(m_set_chan_stmt, 8, chan.IsNSFW);
    Bind(m_set_chan_stmt, 9, chan.LastMessageID);
    Bind(m_set_chan_stmt, 10, chan.Bitrate);
    Bind(m_set_chan_stmt, 11, chan.UserLimit);
    Bind(m_set_chan_stmt, 12, chan.RateLimitPerUser);
    if (chan.Recipients.has_value()) {
        std::vector<Snowflake> ids;
        for (const auto &u : *chan.Recipients)
            ids.push_back(u.ID);
        Bind(m_set_chan_stmt, 13, nlohmann::json(ids).dump());
    } else if (chan.RecipientIDs.has_value()) {
        Bind(m_set_chan_stmt, 13, nlohmann::json(*chan.RecipientIDs).dump());
    } else {
        Bind(m_set_chan_stmt, 13, nullptr);
    }
    Bind(m_set_chan_stmt, 14, chan.Icon);
    Bind(m_set_chan_stmt, 15, chan.OwnerID);
    Bind(m_set_chan_stmt, 16, chan.ApplicationID);
    Bind(m_set_chan_stmt, 17, chan.ParentID);
    Bind(m_set_chan_stmt, 18, chan.LastPinTimestamp);

    if (!RunInsert(m_set_chan_stmt))
        fprintf(stderr, "channel insert failed: %s\n", sqlite3_errstr(m_db_err));

    m_channels.insert(id);
}

void Store::SetEmoji(Snowflake id, const EmojiData &emoji) {
    Bind(m_set_emote_stmt, 1, id);
    Bind(m_set_emote_stmt, 2, emoji.Name);

    if (emoji.Roles.has_value())
        Bind(m_set_emote_stmt, 3, nlohmann::json(*emoji.Roles).dump());
    else
        Bind(m_set_emote_stmt, 3, nullptr);

    if (emoji.Creator.has_value())
        Bind(m_set_emote_stmt, 4, emoji.Creator->ID);
    else
        Bind(m_set_emote_stmt, 4, nullptr);

    Bind(m_set_emote_stmt, 5, emoji.NeedsColons);
    Bind(m_set_emote_stmt, 6, emoji.IsManaged);
    Bind(m_set_emote_stmt, 7, emoji.IsAnimated);
    Bind(m_set_emote_stmt, 8, emoji.IsAvailable);

    if (!RunInsert(m_set_emote_stmt))
        fprintf(stderr, "emoji insert failed: %s\n", sqlite3_errstr(m_db_err));
}

void Store::SetGuild(Snowflake id, const GuildData &guild) {
    Bind(m_set_guild_stmt, 1, id);
    Bind(m_set_guild_stmt, 2, guild.Name);
    Bind(m_set_guild_stmt, 3, guild.Icon);
    Bind(m_set_guild_stmt, 4, guild.Splash);
    Bind(m_set_guild_stmt, 5, guild.IsOwner);
    Bind(m_set_guild_stmt, 6, guild.OwnerID);
    Bind(m_set_guild_stmt, 7, guild.PermissionsNew);
    Bind(m_set_guild_stmt, 8, guild.VoiceRegion);
    Bind(m_set_guild_stmt, 9, guild.AFKChannelID);
    Bind(m_set_guild_stmt, 10, guild.AFKTimeout);
    Bind(m_set_guild_stmt, 11, guild.VerificationLevel);
    Bind(m_set_guild_stmt, 12, guild.DefaultMessageNotifications);
    std::vector<Snowflake> snowflakes;
    if (guild.Roles.has_value()) {
        for (const auto &x : *guild.Roles) snowflakes.push_back(x.ID);
        Bind(m_set_guild_stmt, 13, nlohmann::json(snowflakes).dump());
    } else {
        Bind(m_set_guild_stmt, 13, "[]"s);
    }
    snowflakes.clear();
    if (guild.Emojis.has_value()) {
        for (const auto &x : *guild.Emojis) snowflakes.push_back(x.ID);
        Bind(m_set_guild_stmt, 14, nlohmann::json(snowflakes).dump());
    } else {
        Bind(m_set_guild_stmt, 14, "[]"s);
    }
    if (guild.Features.has_value())
        Bind(m_set_guild_stmt, 15, nlohmann::json(*guild.Features).dump());
    else
        Bind(m_set_guild_stmt, 15, "[]"s);
    Bind(m_set_guild_stmt, 16, guild.MFALevel);
    Bind(m_set_guild_stmt, 17, guild.ApplicationID);
    Bind(m_set_guild_stmt, 18, guild.IsWidgetEnabled);
    Bind(m_set_guild_stmt, 19, guild.WidgetChannelID);
    Bind(m_set_guild_stmt, 20, guild.SystemChannelFlags);
    Bind(m_set_guild_stmt, 21, guild.RulesChannelID);
    Bind(m_set_guild_stmt, 22, guild.JoinedAt);
    Bind(m_set_guild_stmt, 23, guild.IsLarge);
    Bind(m_set_guild_stmt, 24, guild.IsUnavailable);
    Bind(m_set_guild_stmt, 25, guild.MemberCount);
    if (guild.Channels.has_value()) {
        snowflakes.clear();
        for (const auto &x : *guild.Channels) snowflakes.push_back(x.ID);
        Bind(m_set_guild_stmt, 26, nlohmann::json(snowflakes).dump());
    } else
        Bind(m_set_guild_stmt, 26, "[]"s);
    Bind(m_set_guild_stmt, 27, guild.MaxPresences);
    Bind(m_set_guild_stmt, 28, guild.MaxMembers);
    Bind(m_set_guild_stmt, 29, guild.VanityURL);
    Bind(m_set_guild_stmt, 30, guild.Description);
    Bind(m_set_guild_stmt, 31, guild.BannerHash);
    Bind(m_set_guild_stmt, 32, guild.PremiumTier);
    Bind(m_set_guild_stmt, 33, guild.PremiumSubscriptionCount);
    Bind(m_set_guild_stmt, 34, guild.PreferredLocale);
    Bind(m_set_guild_stmt, 35, guild.PublicUpdatesChannelID);
    Bind(m_set_guild_stmt, 36, guild.MaxVideoChannelUsers);
    Bind(m_set_guild_stmt, 37, guild.ApproximateMemberCount);
    Bind(m_set_guild_stmt, 38, guild.ApproximatePresenceCount);
    Bind(m_set_guild_stmt, 39, guild.IsLazy);

    if (!RunInsert(m_set_guild_stmt))
        fprintf(stderr, "guild insert failed: %s\n", sqlite3_errstr(m_db_err));

    m_guilds.insert(id);
}

void Store::SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data) {
    Bind(m_set_member_stmt, 1, user_id);
    Bind(m_set_member_stmt, 2, guild_id);
    Bind(m_set_member_stmt, 3, data.Nickname);
    Bind(m_set_member_stmt, 4, nlohmann::json(data.Roles).dump());
    Bind(m_set_member_stmt, 5, data.JoinedAt);
    Bind(m_set_member_stmt, 6, data.PremiumSince);
    Bind(m_set_member_stmt, 7, data.IsDeafened);
    Bind(m_set_member_stmt, 8, data.IsMuted);

    if (!RunInsert(m_set_member_stmt))
        fprintf(stderr, "member insert failed: %s\n", sqlite3_errstr(m_db_err));
}

void Store::SetMessage(Snowflake id, const Message &message) {
    Bind(m_set_msg_stmt, 1, id);
    Bind(m_set_msg_stmt, 2, message.ChannelID);
    Bind(m_set_msg_stmt, 3, message.GuildID);
    Bind(m_set_msg_stmt, 4, message.Author.ID);
    Bind(m_set_msg_stmt, 5, message.Content);
    Bind(m_set_msg_stmt, 6, message.Timestamp);
    Bind(m_set_msg_stmt, 7, message.EditedTimestamp);
    Bind(m_set_msg_stmt, 8, message.IsTTS);
    Bind(m_set_msg_stmt, 9, message.DoesMentionEveryone);
    Bind(m_set_msg_stmt, 10, nlohmann::json(message.Mentions).dump());
    Bind(m_set_msg_stmt, 11, nlohmann::json(message.Attachments).dump());
    Bind(m_set_msg_stmt, 12, nlohmann::json(message.Embeds).dump());
    Bind(m_set_msg_stmt, 13, message.IsPinned);
    Bind(m_set_msg_stmt, 14, message.WebhookID);
    Bind(m_set_msg_stmt, 15, static_cast<uint64_t>(message.Type));

    if (message.Application.has_value())
        Bind(m_set_msg_stmt, 16, nlohmann::json(*message.Application).dump());
    else
        Bind(m_set_msg_stmt, 16, nullptr);

    if (message.MessageReference.has_value())
        Bind(m_set_msg_stmt, 17, nlohmann::json(*message.MessageReference).dump());
    else
        Bind(m_set_msg_stmt, 17, nullptr);

    if (message.Flags.has_value())
        Bind(m_set_msg_stmt, 18, static_cast<uint64_t>(*message.Flags));
    else
        Bind(m_set_msg_stmt, 18, nullptr);

    if (message.Stickers.has_value())
        Bind(m_set_msg_stmt, 19, nlohmann::json(*message.Stickers).dump());
    else
        Bind(m_set_msg_stmt, 19, nullptr);

    if (message.Reactions.has_value()) {
        std::string tmp = nlohmann::json(*message.Reactions).dump();
        Bind(m_set_msg_stmt, 20, tmp);
    } else
        Bind(m_set_msg_stmt, 20, nullptr);
    Bind(m_set_msg_stmt, 21, message.IsDeleted());
    Bind(m_set_msg_stmt, 22, message.IsEdited());
    Bind(m_set_msg_stmt, 23, message.IsPending);
    Bind(m_set_msg_stmt, 24, message.Nonce); // sorry

    if (!RunInsert(m_set_msg_stmt))
        fprintf(stderr, "message insert failed: %s\n", sqlite3_errstr(m_db_err));

    if (message.Interaction.has_value())
        SetMessageInteractionPair(id, *message.Interaction);
}

void Store::SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm) {
    Bind(m_set_perm_stmt, 1, perm.ID);
    Bind(m_set_perm_stmt, 2, channel_id);
    Bind(m_set_perm_stmt, 3, static_cast<int>(perm.Type));
    Bind(m_set_perm_stmt, 4, static_cast<uint64_t>(perm.Allow));
    Bind(m_set_perm_stmt, 5, static_cast<uint64_t>(perm.Deny));

    if (!RunInsert(m_set_perm_stmt))
        fprintf(stderr, "permission insert failed: %s\n", sqlite3_errstr(m_db_err));
}

void Store::SetRole(Snowflake id, const RoleData &role) {
    Bind(m_set_role_stmt, 1, id);
    Bind(m_set_role_stmt, 2, role.Name);
    Bind(m_set_role_stmt, 3, role.Color);
    Bind(m_set_role_stmt, 4, role.IsHoisted);
    Bind(m_set_role_stmt, 5, role.Position);
    Bind(m_set_role_stmt, 6, static_cast<uint64_t>(role.Permissions));
    Bind(m_set_role_stmt, 7, role.IsManaged);
    Bind(m_set_role_stmt, 8, role.IsMentionable);

    if (!RunInsert(m_set_role_stmt))
        fprintf(stderr, "role insert failed: %s\n", sqlite3_errstr(m_db_err));
}

void Store::SetUser(Snowflake id, const UserData &user) {
    Bind(m_set_user_stmt, 1, id);
    Bind(m_set_user_stmt, 2, user.Username);
    Bind(m_set_user_stmt, 3, user.Discriminator);
    Bind(m_set_user_stmt, 4, user.Avatar);
    Bind(m_set_user_stmt, 5, user.IsBot);
    Bind(m_set_user_stmt, 6, user.IsSystem);
    Bind(m_set_user_stmt, 7, user.IsMFAEnabled);
    Bind(m_set_user_stmt, 8, user.Locale);
    Bind(m_set_user_stmt, 9, user.IsVerified);
    Bind(m_set_user_stmt, 10, user.Email);
    Bind(m_set_user_stmt, 11, user.Flags);
    Bind(m_set_user_stmt, 12, user.PremiumType);
    Bind(m_set_user_stmt, 13, user.PublicFlags);

    if (!RunInsert(m_set_user_stmt)) {
        fprintf(stderr, "user insert failed: %s\n", sqlite3_errstr(m_db_err));
    }
}

void Store::SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction) {
    Bind(m_set_msg_interaction_stmt, 1, message_id);
    Bind(m_set_msg_interaction_stmt, 2, interaction.ID);
    Bind(m_set_msg_interaction_stmt, 3, interaction.Type);
    Bind(m_set_msg_interaction_stmt, 4, interaction.Name);
    Bind(m_set_msg_interaction_stmt, 5, interaction.User.ID);

    if (!RunInsert(m_set_msg_interaction_stmt)) {
        fprintf(stderr, "message interaction insert failed: %s\n", sqlite3_errstr(m_db_err));
    }
}

std::optional<BanData> Store::GetBan(Snowflake guild_id, Snowflake user_id) const {
    Bind(m_get_ban_stmt, 1, guild_id);
    Bind(m_get_ban_stmt, 2, user_id);
    if (!FetchOne(m_get_ban_stmt)) {
        if (m_db_err != SQLITE_DONE)
            fprintf(stderr, "error while fetching ban: %s\n", sqlite3_errstr(m_db_err));
        Reset(m_get_ban_stmt);
        return std::nullopt;
    }

    BanData ret;
    ret.User.ID = user_id;
    Get(m_get_ban_stmt, 2, ret.Reason);

    Reset(m_get_ban_stmt);
    return ret;
}

std::vector<BanData> Store::GetBans(Snowflake guild_id) const {
    Bind(m_get_bans_stmt, 1, guild_id);

    std::vector<BanData> ret;
    while (FetchOne(m_get_bans_stmt)) {
        auto &ban = ret.emplace_back();
        Get(m_get_bans_stmt, 1, ban.User.ID);
        Get(m_get_bans_stmt, 2, ban.Reason);
    }

    Reset(m_get_bans_stmt);

    if (m_db_err != SQLITE_DONE)
        fprintf(stderr, "error while fetching bans: %s\n", sqlite3_errstr(m_db_err));
    return ret;
}

std::optional<ChannelData> Store::GetChannel(Snowflake id) const {
    Bind(m_get_chan_stmt, 1, id);
    if (!FetchOne(m_get_chan_stmt)) {
        if (m_db_err != SQLITE_DONE)
            fprintf(stderr, "error while fetching channel: %s\n", sqlite3_errstr(m_db_err));
        Reset(m_get_chan_stmt);
        return std::nullopt;
    }

    ChannelData ret;
    ret.ID = id;
    int tmpi;
    Get(m_get_chan_stmt, 1, tmpi);
    ret.Type = static_cast<ChannelType>(tmpi);
    Get(m_get_chan_stmt, 2, ret.GuildID);
    Get(m_get_chan_stmt, 3, ret.Position);
    ret.PermissionOverwrites = std::nullopt;
    Get(m_get_chan_stmt, 5, ret.Name);
    Get(m_get_chan_stmt, 6, ret.Topic);
    Get(m_get_chan_stmt, 7, ret.IsNSFW);
    Get(m_get_chan_stmt, 8, ret.LastMessageID);
    Get(m_get_chan_stmt, 9, ret.Bitrate);
    Get(m_get_chan_stmt, 10, ret.UserLimit);
    Get(m_get_chan_stmt, 11, ret.RateLimitPerUser);
    if (!IsNull(m_get_chan_stmt, 12)) {
        std::string tmps;
        Get(m_get_chan_stmt, 12, tmps);
        ret.RecipientIDs = nlohmann::json::parse(tmps).get<std::vector<Snowflake>>();
    }
    Get(m_get_chan_stmt, 13, ret.Icon);
    Get(m_get_chan_stmt, 14, ret.OwnerID);
    Get(m_get_chan_stmt, 15, ret.ApplicationID);
    Get(m_get_chan_stmt, 16, ret.ParentID);
    Get(m_get_chan_stmt, 17, ret.LastPinTimestamp);

    Reset(m_get_chan_stmt);

    return ret;
}

std::optional<EmojiData> Store::GetEmoji(Snowflake id) const {
    Bind(m_get_emote_stmt, 1, id);
    if (!FetchOne(m_get_emote_stmt)) {
        if (m_db_err != SQLITE_DONE)
            fprintf(stderr, "error while fetching emoji: %s\n", sqlite3_errstr(m_db_err));
        Reset(m_get_emote_stmt);
        return std::nullopt;
    }

    EmojiData ret;
    ret.ID = id;
    Get(m_get_emote_stmt, 1, ret.Name);

    if (!IsNull(m_get_emote_stmt, 2)) {
        std::string tmp;
        Get(m_get_emote_stmt, 2, tmp);
        ret.Roles = nlohmann::json::parse(tmp).get<std::vector<Snowflake>>();
    }

    if (!IsNull(m_get_emote_stmt, 3)) {
        ret.Creator = std::optional<UserData>(UserData());
        Get(m_get_emote_stmt, 3, ret.Creator->ID);
    }
    Get(m_get_emote_stmt, 4, ret.NeedsColons);
    Get(m_get_emote_stmt, 5, ret.IsManaged);
    Get(m_get_emote_stmt, 6, ret.IsAnimated);
    Get(m_get_emote_stmt, 7, ret.IsAvailable);

    Reset(m_get_emote_stmt);

    return ret;
}

std::optional<GuildData> Store::GetGuild(Snowflake id) const {
    Bind(m_get_guild_stmt, 1, id);
    if (!FetchOne(m_get_guild_stmt)) {
        if (m_db_err != SQLITE_DONE)
            fprintf(stderr, "error while fetching guild: %s\n", sqlite3_errstr(m_db_err));
        Reset(m_get_guild_stmt);
        return std::nullopt;
    }

    GuildData ret;
    ret.ID = id;
    Get(m_get_guild_stmt, 1, ret.Name);
    Get(m_get_guild_stmt, 2, ret.Icon);
    Get(m_get_guild_stmt, 3, ret.Splash);
    Get(m_get_guild_stmt, 4, ret.IsOwner);
    Get(m_get_guild_stmt, 5, ret.OwnerID);
    Get(m_get_guild_stmt, 6, ret.PermissionsNew);
    Get(m_get_guild_stmt, 7, ret.VoiceRegion);
    Get(m_get_guild_stmt, 8, ret.AFKChannelID);
    Get(m_get_guild_stmt, 9, ret.AFKTimeout);
    Get(m_get_guild_stmt, 10, ret.VerificationLevel);
    Get(m_get_guild_stmt, 11, ret.DefaultMessageNotifications);
    std::string tmp;
    Get(m_get_guild_stmt, 12, tmp);
    ret.Roles.emplace();
    for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
        ret.Roles->emplace_back().ID = id;
    Get(m_get_guild_stmt, 13, tmp);
    ret.Emojis.emplace();
    for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
        ret.Emojis->emplace_back().ID = id;
    Get(m_get_guild_stmt, 14, tmp);
    ret.Features = nlohmann::json::parse(tmp).get<std::unordered_set<std::string>>();
    Get(m_get_guild_stmt, 15, ret.MFALevel);
    Get(m_get_guild_stmt, 16, ret.ApplicationID);
    Get(m_get_guild_stmt, 17, ret.IsWidgetEnabled);
    Get(m_get_guild_stmt, 18, ret.WidgetChannelID);
    Get(m_get_guild_stmt, 19, ret.SystemChannelFlags);
    Get(m_get_guild_stmt, 20, ret.RulesChannelID);
    Get(m_get_guild_stmt, 21, ret.JoinedAt);
    Get(m_get_guild_stmt, 22, ret.IsLarge);
    Get(m_get_guild_stmt, 23, ret.IsUnavailable);
    Get(m_get_guild_stmt, 24, ret.MemberCount);
    Get(m_get_guild_stmt, 25, tmp);
    ret.Channels.emplace();
    for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
        ret.Channels->emplace_back().ID = id;
    Get(m_get_guild_stmt, 26, ret.MaxPresences);
    Get(m_get_guild_stmt, 27, ret.MaxMembers);
    Get(m_get_guild_stmt, 28, ret.VanityURL);
    Get(m_get_guild_stmt, 29, ret.Description);
    Get(m_get_guild_stmt, 30, ret.BannerHash);
    Get(m_get_guild_stmt, 31, ret.PremiumTier);
    Get(m_get_guild_stmt, 32, ret.PremiumSubscriptionCount);
    Get(m_get_guild_stmt, 33, ret.PreferredLocale);
    Get(m_get_guild_stmt, 34, ret.PublicUpdatesChannelID);
    Get(m_get_guild_stmt, 35, ret.MaxVideoChannelUsers);
    Get(m_get_guild_stmt, 36, ret.ApproximateMemberCount);
    Get(m_get_guild_stmt, 37, ret.ApproximatePresenceCount);
    Get(m_get_guild_stmt, 38, ret.IsLazy);

    Reset(m_get_guild_stmt);

    return ret;
}

std::optional<GuildMember> Store::GetGuildMember(Snowflake guild_id, Snowflake user_id) const {
    Bind(m_get_member_stmt, 1, user_id);
    Bind(m_get_member_stmt, 2, guild_id);
    if (!FetchOne(m_get_member_stmt)) {
        if (m_db_err != SQLITE_DONE)
            fprintf(stderr, "error while fetching member: %s\n", sqlite3_errstr(m_db_err));
        Reset(m_get_member_stmt);
        return std::nullopt;
    }

    GuildMember ret;
    ret.User.emplace().ID = user_id;
    Get(m_get_member_stmt, 2, ret.Nickname);
    std::string tmp;
    Get(m_get_member_stmt, 3, tmp);
    ret.Roles = nlohmann::json::parse(tmp).get<std::vector<Snowflake>>();
    Get(m_get_member_stmt, 4, ret.JoinedAt);
    Get(m_get_member_stmt, 5, ret.PremiumSince);
    Get(m_get_member_stmt, 6, ret.IsDeafened);
    Get(m_get_member_stmt, 7, ret.IsMuted);

    Reset(m_get_member_stmt);

    return ret;
}

std::optional<Message> Store::GetMessage(Snowflake id) const {
    Bind(m_get_msg_stmt, 1, id);
    if (!FetchOne(m_get_msg_stmt)) {
        if (m_db_err != SQLITE_DONE)
            fprintf(stderr, "error while fetching message: %s\n", sqlite3_errstr(m_db_err));
        Reset(m_get_msg_stmt);
        return std::nullopt;
    }

    Message ret;
    ret.ID = id;
    Get(m_get_msg_stmt, 1, ret.ChannelID);
    Get(m_get_msg_stmt, 2, ret.GuildID);
    Get(m_get_msg_stmt, 3, ret.Author.ID); // yike
    Get(m_get_msg_stmt, 4, ret.Content);
    Get(m_get_msg_stmt, 5, ret.Timestamp);
    Get(m_get_msg_stmt, 6, ret.EditedTimestamp);
    Get(m_get_msg_stmt, 7, ret.IsTTS);
    Get(m_get_msg_stmt, 8, ret.DoesMentionEveryone);
    std::string tmps;
    Get(m_get_msg_stmt, 9, tmps);
    nlohmann::json::parse(tmps).get_to(ret.Mentions);
    Get(m_get_msg_stmt, 10, tmps);
    nlohmann::json::parse(tmps).get_to(ret.Attachments);
    Get(m_get_msg_stmt, 11, tmps);
    nlohmann::json::parse(tmps).get_to(ret.Embeds);
    Get(m_get_msg_stmt, 12, ret.IsPinned);
    Get(m_get_msg_stmt, 13, ret.WebhookID);
    uint64_t tmpi;
    Get(m_get_msg_stmt, 14, tmpi);
    ret.Type = static_cast<MessageType>(tmpi);

    Get(m_get_msg_stmt, 15, tmps);
    if (tmps != "")
        ret.Application = nlohmann::json::parse(tmps).get<MessageApplicationData>();

    Get(m_get_msg_stmt, 16, tmps);
    if (tmps != "")
        ret.MessageReference = nlohmann::json::parse(tmps).get<MessageReferenceData>();

    Get(m_get_msg_stmt, 17, tmpi);
    ret.Flags = static_cast<MessageFlags>(tmpi);

    Get(m_get_msg_stmt, 18, tmps);
    if (tmps != "")
        ret.Stickers = nlohmann::json::parse(tmps).get<std::vector<StickerData>>();

    Get(m_get_msg_stmt, 19, tmps);
    if (tmps != "")
        ret.Reactions = nlohmann::json::parse(tmps).get<std::vector<ReactionData>>();

    bool tmpb = false;
    Get(m_get_msg_stmt, 20, tmpb);
    if (tmpb) ret.SetDeleted();

    Get(m_get_msg_stmt, 21, tmpb);
    if (tmpb) ret.SetEdited();

    Get(m_get_msg_stmt, 22, ret.IsPending);
    Get(m_get_msg_stmt, 23, ret.Nonce);

    // interaction data from join

    if (!IsNull(m_get_msg_stmt, 24)) {
        auto &interaction = ret.Interaction.emplace();
        Get(m_get_msg_stmt, 24, interaction.ID);
        Get(m_get_msg_stmt, 25, interaction.Name);
        Get(m_get_msg_stmt, 26, interaction.Type);
        Get(m_get_msg_stmt, 27, interaction.User.ID);
    }

    Reset(m_get_msg_stmt);

    if (ret.MessageReference.has_value() && ret.MessageReference->MessageID.has_value()) {
        auto ref = GetMessage(*ret.MessageReference->MessageID);
        if (ref.has_value())
            ret.ReferencedMessage = std::make_unique<Message>(std::move(*ref));
        else
            ret.ReferencedMessage = nullptr;
    }

    return std::optional<Message>(std::move(ret));
}

std::optional<PermissionOverwrite> Store::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const {
    Bind(m_get_perm_stmt, 1, id);
    Bind(m_get_perm_stmt, 2, channel_id);
    if (!FetchOne(m_get_perm_stmt)) {
        if (m_db_err != SQLITE_DONE)
            fprintf(stderr, "error while fetching permission: %s\n", sqlite3_errstr(m_db_err));
        Reset(m_get_perm_stmt);
        return std::nullopt;
    }

    PermissionOverwrite ret;
    ret.ID = id;
    uint64_t tmp;
    Get(m_get_perm_stmt, 2, tmp);
    ret.Type = static_cast<PermissionOverwrite::OverwriteType>(tmp);
    Get(m_get_perm_stmt, 3, tmp);
    ret.Allow = static_cast<Permission>(tmp);
    Get(m_get_perm_stmt, 4, tmp);
    ret.Deny = static_cast<Permission>(tmp);

    Reset(m_get_perm_stmt);

    return ret;
}

std::optional<RoleData> Store::GetRole(Snowflake id) const {
    Bind(m_get_role_stmt, 1, id);
    if (!FetchOne(m_get_role_stmt)) {
        if (m_db_err != SQLITE_DONE)
            fprintf(stderr, "error while fetching role: %s\n", sqlite3_errstr(m_db_err));
        Reset(m_get_role_stmt);
        return std::nullopt;
    }

    RoleData ret;
    ret.ID = id;
    Get(m_get_role_stmt, 1, ret.Name);
    Get(m_get_role_stmt, 2, ret.Color);
    Get(m_get_role_stmt, 3, ret.IsHoisted);
    Get(m_get_role_stmt, 4, ret.Position);
    uint64_t tmp;
    Get(m_get_role_stmt, 5, tmp);
    ret.Permissions = static_cast<Permission>(tmp);
    Get(m_get_role_stmt, 6, ret.IsManaged);
    Get(m_get_role_stmt, 7, ret.IsMentionable);

    Reset(m_get_role_stmt);

    return ret;
}

std::optional<UserData> Store::GetUser(Snowflake id) const {
    Bind(m_get_user_stmt, 1, id);
    if (!FetchOne(m_get_user_stmt)) {
        if (m_db_err != SQLITE_DONE)
            fprintf(stderr, "error while fetching user info: %s\n", sqlite3_errstr(m_db_err));
        Reset(m_get_user_stmt);
        return std::nullopt;
    }

    UserData ret;
    Get(m_get_user_stmt, 0, ret.ID);
    Get(m_get_user_stmt, 1, ret.Username);
    Get(m_get_user_stmt, 2, ret.Discriminator);
    Get(m_get_user_stmt, 3, ret.Avatar);
    Get(m_get_user_stmt, 4, ret.IsBot);
    Get(m_get_user_stmt, 5, ret.IsSystem);
    Get(m_get_user_stmt, 6, ret.IsMFAEnabled);
    Get(m_get_user_stmt, 7, ret.Locale);
    Get(m_get_user_stmt, 8, ret.IsVerified);
    Get(m_get_user_stmt, 9, ret.Email);
    Get(m_get_user_stmt, 10, ret.Flags);
    Get(m_get_user_stmt, 11, ret.PremiumType);
    Get(m_get_user_stmt, 12, ret.PublicFlags);

    Reset(m_get_user_stmt);

    return ret;
}

void Store::ClearGuild(Snowflake id) {
    m_guilds.erase(id);
}

void Store::ClearChannel(Snowflake id) {
    m_channels.erase(id);
}

void Store::ClearBan(Snowflake guild_id, Snowflake user_id) {
    Bind(m_clear_ban_stmt, 1, guild_id);
    Bind(m_clear_ban_stmt, 2, user_id);

    if ((m_db_err = sqlite3_step(m_clear_ban_stmt)) != SQLITE_DONE)
        printf("clearing ban failed: %s\n", sqlite3_errstr(m_db_err));

    Reset(m_clear_ban_stmt);
}

const std::unordered_set<Snowflake> &Store::GetChannels() const {
    return m_channels;
}

const std::unordered_set<Snowflake> &Store::GetGuilds() const {
    return m_guilds;
}
void Store::ClearAll() {
    m_channels.clear();
    m_guilds.clear();
}

void Store::BeginTransaction() {
    m_db_err = sqlite3_exec(m_db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr);
}

void Store::EndTransaction() {
    m_db_err = sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, nullptr);
}

bool Store::CreateTables() {
    constexpr const char *create_users = R"(
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY,
            username TEXT NOT NULL,
            discriminator TEXT NOT NULL,
            avatar TEXT,
            bot BOOL,
            system BOOL,
            mfa BOOL,
            locale TEXT,
            verified BOOl,
            email TEXT,
            flags INTEGER,
            premium INTEGER,
            pubflags INTEGER
        )
    )";

    constexpr const char *create_permissions = R"(
        CREATE TABLE IF NOT EXISTS permissions (
            id INTEGER NOT NULL,
            channel_id INTEGER NOT NULL,
            type INTEGER NOT NULL,
            allow INTEGER NOT NULL,
            deny INTEGER NOT NULL
        )
    )";

    constexpr const char *create_messages = R"(
        CREATE TABLE IF NOT EXISTS messages (
            id INTEGER PRIMARY KEY,
            channel_id INTEGER NOT NULL,
            guild_id INTEGER,
            author_id INTEGER NOT NULL,
            content TEXT NOT NULL,
            timestamp TEXT NOT NULL,
            edited_timestamp TEXT,
            tts BOOL NOT NULL,
            everyone BOOL NOT NULL,
            mentions TEXT NOT NULL, /* json */
            attachments TEXT NOT NULL, /* json */
            embeds TEXT NOT NULL, /* json */
            pinned BOOL,
            webhook_id INTEGER,
            type INTEGER,
            application TEXT, /* json */
            reference TEXT, /* json */
            flags INTEGER,
            stickers TEXT, /* json */
            reactions TEXT, /* json */
            deleted BOOL, /* extra */
            edited BOOL, /* extra */
            pending BOOL, /* extra */
            nonce TEXT
        )
    )";

    constexpr const char *create_roles = R"(
        CREATE TABLE IF NOT EXISTS roles (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            color INTEGER NOT NULL,
            hoisted BOOL NOT NULL,
            position INTEGER NOT NULL,
            permissions INTEGER NOT NULL,
            managed BOOL NOT NULL,
            mentionable BOOL NOT NULL
        )
    )";

    constexpr const char *create_emojis = R"(
        CREATE TABLE IF NOT EXISTS emojis (
            id INTEGER PRIMARY KEY, /*though nullable, only custom emojis (with non-null ids) are stored*/
            name TEXT NOT NULL, /*same as id*/
            roles TEXT, /* json */
            creator_id INTEGER,
            colons BOOL,
            managed BOOL,
            animated BOOL,
            available BOOL
        )
    )";

    constexpr const char *create_members = R"(
        CREATE TABLE IF NOT EXISTS members (
            user_id INTEGER NOT NULL,
            guild_id INTEGER NOT NULL,
            nickname TEXT,
            roles TEXT NOT NULL, /* json */
            joined_at TEXT NOT NULL,
            premium_since TEXT,
            deaf BOOL NOT NULL,
            mute BOOL NOT NULL,
            PRIMARY KEY(user_id, guild_id)
        )
    )";

    constexpr const char *create_guilds = R"(
        CREATE TABLE IF NOT EXISTS guilds (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            icon TEXT NOT NULL,
            splash TEXT,
            owner BOOL,
            owner_id INTEGER NOT NULL,
            permissions INTEGER, /* new */
            voice_region TEXT,
            afk_id INTEGER,
            afk_timeout INTEGER NOT NULL,
            verification INTEGER NOT NULL,
            notifications INTEGER NOT NULL,
            roles TEXT NOT NULL, /* json */
            emojis TEXT NOT NULL, /* json */
            features TEXT NOT NULL, /* json */
            mfa INTEGER NOT NULL,
            application INTEGER,
            widget BOOL,
            widget_channel INTEGER,
            system_flags INTEGER NOT NULL,
            rules_channel INTEGER,
            joined_at TEXT,
            large BOOL,
            unavailable BOOL,
            member_count INTEGER,
            channels TEXT NOT NULL, /* json */
            max_presences INTEGER,
            max_members INTEGER,
            vanity TEXT,
            description TEXT,
            banner_hash TEXT,
            premium_tier INTEGER NOT NULL,
            premium_count INTEGER,
            locale TEXT NOT NULL,
            public_updates_id INTEGER,
            max_video_users INTEGER,
            approx_members INTEGER,
            approx_presences INTEGER,
            lazy BOOL
        )
    )";

    constexpr const char *create_channels = R"(
        CREATE TABLE IF NOT EXISTS channels (
            id INTEGER PRIMARY KEY,
            type INTEGER NOT NULL,
            guild_id INTEGER,
            position INTEGER,
            overwrites TEXT, /* json */
            name TEXT,
            topic TEXT,
            is_nsfw BOOL,
            last_message_id INTEGER,
            bitrate INTEGER,
            user_limit INTEGER,
            rate_limit INTEGER,
            recipients TEXT, /* json */
            icon TEXT,
            owner_id INTEGER,
            application_id INTEGER,
            parent_id INTEGER,
            last_pin_timestamp TEXT
        )
    )";

    constexpr const char *create_bans = R"(
        CREATE TABLE IF NOT EXISTS bans (
            guild_id INTEGER NOT NULL,
            user_id INTEGER NOT NULL,
            reason TEXT,
            PRIMARY KEY(user_id, guild_id)
        )
    )";

    constexpr const char *create_interactions = R"(
        CREATE TABLE IF NOT EXISTS message_interactions (
            message_id INTEGER NOT NULL,
            interaction_id INTEGER NOT NULL,
            type INTEGER NOT NULL,
            name STRING NOT NULL,
            user_id INTEGER NOT NULL,
            PRIMARY KEY(message_id)
        )
    )";

    m_db_err = sqlite3_exec(m_db, create_users, nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to create user table: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_exec(m_db, create_permissions, nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to create permissions table: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_exec(m_db, create_messages, nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to create messages table: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_exec(m_db, create_roles, nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to create roles table: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_exec(m_db, create_emojis, nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "faile to create emojis table: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_exec(m_db, create_members, nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to create members table: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_exec(m_db, create_guilds, nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to create guilds table: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_exec(m_db, create_channels, nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to create channels table: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_exec(m_db, create_bans, nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to create bans table: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_exec(m_db, create_interactions, nullptr, nullptr, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to create message interactions table: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    return true;
}

bool Store::CreateStatements() {
    constexpr const char *set_user = R"(
        REPLACE INTO users VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )
    )";

    constexpr const char *get_user = R"(
        SELECT * FROM users WHERE id = ?
    )";

    constexpr const char *set_perm = R"(
        REPLACE INTO permissions VALUES (
            ?, ?, ?, ?, ?
        )
    )";

    constexpr const char *get_perm = R"(
        SELECT * FROM permissions WHERE id = ? AND channel_id = ?
    )";

    constexpr const char *set_msg = R"(
        REPLACE INTO messages VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )
    )";

    constexpr const char *get_msg = R"(
        SELECT messages.*,
               message_interactions.interaction_id as interaction_id,
	           message_interactions.name as interaction_name,
	           message_interactions.type as interaction_type,
               message_interactions.user_id as interaction_user_id
        FROM messages
        LEFT OUTER JOIN
            message_interactions
                ON messages.id = message_interactions.message_id
        WHERE id = ?
    )";

    constexpr const char *set_role = R"(
        REPLACE INTO roles VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?
        )
    )";

    constexpr const char *get_role = R"(
        SELECT * FROM roles WHERE id = ?
    )";

    constexpr const char *set_emoji = R"(
        REPLACE INTO emojis VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?
        )
    )";

    constexpr const char *get_emoji = R"(
        SELECT * FROM emojis WHERE id = ?
    )";

    constexpr const char *set_member = R"(
        REPLACE INTO members VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?
        )
    )";

    constexpr const char *get_member = R"(
        SELECT * FROM members WHERE user_id = ? AND guild_id = ?
    )";

    constexpr const char *set_guild = R"(
        REPLACE INTO guilds VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )
    )";

    constexpr const char *get_guild = R"(
        SELECT * FROM guilds WHERE id = ?
    )";

    constexpr const char *set_chan = R"(
        REPLACE INTO channels VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )
    )";

    constexpr const char *get_chan = R"(
        SELECT * FROM channels WHERE id = ?
    )";

    constexpr const char *set_ban = R"(
        REPLACE INTO bans VALUES (
            ?, ?, ?
        )
    )";

    constexpr const char *get_ban = R"(
        SELECT * FROM bans WHERE guild_id = ? AND user_id = ?
    )";

    constexpr const char *clear_ban = R"(
        DELETE FROM bans WHERE guild_id = ? AND user_id = ?
    )";

    constexpr const char *get_bans = R"(
        SELECT * FROM bans WHERE guild_id = ?
    )";

    constexpr const char *set_interaction = R"(
        REPLACE INTO message_interactions VALUES (
            ?, ?, ?, ?, ?
        )
    )";

    m_db_err = sqlite3_prepare_v2(m_db, set_user, -1, &m_set_user_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare set user statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, get_user, -1, &m_get_user_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare get user statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, set_perm, -1, &m_set_perm_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare set permission statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, get_perm, -1, &m_get_perm_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare get permission statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, set_msg, -1, &m_set_msg_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare set message statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, get_msg, -1, &m_get_msg_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare get message statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, set_role, -1, &m_set_role_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare set role statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, get_role, -1, &m_get_role_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare get role statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, set_emoji, -1, &m_set_emote_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare set emoji statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, get_emoji, -1, &m_get_emote_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare get emoji statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, set_member, -1, &m_set_member_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare set member statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, get_member, -1, &m_get_member_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare get member statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, set_guild, -1, &m_set_guild_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare set guild statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, get_guild, -1, &m_get_guild_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare get guild statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, set_chan, -1, &m_set_chan_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare set channel statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, get_chan, -1, &m_get_chan_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare get channel statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, set_ban, -1, &m_set_ban_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare set ban statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, get_ban, -1, &m_get_ban_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare get ban statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, clear_ban, -1, &m_clear_ban_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare clear ban statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, get_bans, -1, &m_get_bans_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare get bans statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    m_db_err = sqlite3_prepare_v2(m_db, set_interaction, -1, &m_set_msg_interaction_stmt, nullptr);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "failed to prepare set message interaction statement: %s\n", sqlite3_errstr(m_db_err));
        return false;
    }

    return true;
}

void Store::Cleanup() {
    sqlite3_finalize(m_set_user_stmt);
    sqlite3_finalize(m_get_user_stmt);
    sqlite3_finalize(m_set_perm_stmt);
    sqlite3_finalize(m_get_perm_stmt);
    sqlite3_finalize(m_set_msg_stmt);
    sqlite3_finalize(m_get_msg_stmt);
    sqlite3_finalize(m_set_role_stmt);
    sqlite3_finalize(m_get_role_stmt);
    sqlite3_finalize(m_set_emote_stmt);
    sqlite3_finalize(m_get_emote_stmt);
    sqlite3_finalize(m_set_member_stmt);
    sqlite3_finalize(m_get_member_stmt);
    sqlite3_finalize(m_set_guild_stmt);
    sqlite3_finalize(m_get_guild_stmt);
    sqlite3_finalize(m_set_chan_stmt);
    sqlite3_finalize(m_get_chan_stmt);
    sqlite3_finalize(m_set_ban_stmt);
    sqlite3_finalize(m_get_ban_stmt);
    sqlite3_finalize(m_clear_ban_stmt);
    sqlite3_finalize(m_get_bans_stmt);
    sqlite3_finalize(m_set_msg_interaction_stmt);
}

void Store::Bind(sqlite3_stmt *stmt, int index, int num) const {
    m_db_err = sqlite3_bind_int(stmt, index, num);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
    }
}

void Store::Bind(sqlite3_stmt *stmt, int index, uint64_t num) const {
    m_db_err = sqlite3_bind_int64(stmt, index, num);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
    }
}

void Store::Bind(sqlite3_stmt *stmt, int index, const std::string &str) const {
    m_db_err = sqlite3_bind_blob(stmt, index, str.c_str(), str.length(), SQLITE_TRANSIENT);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
    }
}

void Store::Bind(sqlite3_stmt *stmt, int index, bool val) const {
    m_db_err = sqlite3_bind_int(stmt, index, val ? 1 : 0);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
    }
}

void Store::Bind(sqlite3_stmt *stmt, int index, std::nullptr_t) const {
    m_db_err = sqlite3_bind_null(stmt, index);
    if (m_db_err != SQLITE_OK) {
        fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
    }
}

bool Store::RunInsert(sqlite3_stmt *stmt) {
    m_db_err = sqlite3_step(stmt);
    Reset(stmt);
    return m_db_err == SQLITE_DONE;
}

bool Store::FetchOne(sqlite3_stmt *stmt) const {
    m_db_err = sqlite3_step(stmt);
    return m_db_err == SQLITE_ROW;
}

void Store::Get(sqlite3_stmt *stmt, int index, int &out) const {
    out = sqlite3_column_int(stmt, index);
}

void Store::Get(sqlite3_stmt *stmt, int index, uint64_t &out) const {
    out = sqlite3_column_int64(stmt, index);
}

void Store::Get(sqlite3_stmt *stmt, int index, std::string &out) const {
    const unsigned char *ptr = sqlite3_column_text(stmt, index);
    if (ptr == nullptr)
        out = "";
    else
        out = reinterpret_cast<const char *>(ptr);
}

void Store::Get(sqlite3_stmt *stmt, int index, bool &out) const {
    out = sqlite3_column_int(stmt, index) != 0;
}

void Store::Get(sqlite3_stmt *stmt, int index, Snowflake &out) const {
    const int64_t num = sqlite3_column_int64(stmt, index);
    out = static_cast<uint64_t>(num);
}

bool Store::IsNull(sqlite3_stmt *stmt, int index) const {
    return sqlite3_column_type(stmt, index) == SQLITE_NULL;
}

void Store::Reset(sqlite3_stmt *stmt) const {
    sqlite3_reset(stmt);
    sqlite3_clear_bindings(stmt);
}
