#ifndef GATEWAY_WEBSOCKET_HPP_
#define GATEWAY_WEBSOCKET_HPP_
#include <string>
#include <thread>
#include <iostream>
#include <chrono>
#include <utils/types.hpp>
#include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXWebSocket.h>
using json = nlohmann::json;
using std::cout;
using std::endl;
using namespace std::chrono;
using namespace std::chrono_literals;
class WebSocket {
private:
    const std::array<std::pair<const int, std::string_view>, 73> events = { {
        {APPLICATION_COMMAND_PERMISSIONS_UPDATE, "APPLICATION_COMMAND_PERMISSIONS_UPDATE"},
        {AUTO_MODERATION_ACTION_EXECUTION, "AUTO_MODERATION_ACTION_EXECUTION"},
        {AUTO_MODERATION_RULE_CREATE, "AUTO_MODERATION_RULE_CREATE"},
        {AUTO_MODERATION_RULE_DELETE, "AUTO_MODERATION_RULE_DELETE"},
        {AUTO_MODERATION_RULE_UPDATE, "AUTO_MODERATION_RULE_UPDATE"},
        {CHANNEL_CREATE, "CHANNEL_CREATE"},
        {CHANNEL_DELETE, "CHANNEL_DELETE"},
        {CHANNEL_PINS_UPDATE, "CHANNEL_PINS_UPDATE"},
        {CHANNEL_UPDATE, "CHANNEL_UPDATE"},
        {ENTITLEMENT_CREATE, "ENTITLEMENT_CREATE"},
        {ENTITLEMENT_DELETE, "ENTITLEMENT_DELETE"},
        {ENTITLEMENT_UPDATE, "ENTITLEMENT_UPDATE"},
        {GUILD_AUDIT_LOG_ENTRY_CREATE, "GUILD_AUDIT_LOG_ENTRY_CREATE"},
        {GUILD_BAN_ADD, "GUILD_BAN_ADD"},
        {GUILD_BAN_REMOVE, "GUILD_BAN_REMOVE"},
        {GUILD_CREATE, "GUILD_CREATE"},
        {GUILD_DELETE, "GUILD_DELETE"},
        {GUILD_EMOJIS_UPDATE, "GUILD_EMOJIS_UPDATE"},
        {GUILD_INTEGRATIONS_UPDATE, "GUILD_INTEGRATIONS_UPDATE"},
        {GUILD_MEMBER_ADD, "GUILD_MEMBER_ADD"},
        {GUILD_MEMBER_REMOVE, "GUILD_MEMBER_REMOVE"},
        {GUILD_MEMBERS_CHUNK, "GUILD_MEMBERS_CHUNK"},
        {GUILD_MEMBER_UPDATE, "GUILD_MEMBER_UPDATE"},
        {GUILD_ROLE_CREATE, "GUILD_ROLE_CREATE"},
        {GUILD_ROLE_DELETE, "GUILD_ROLE_DELETE"},
        {GUILD_ROLE_UPDATE, "GUILD_ROLE_UPDATE"},
        {GUILD_SCHEDULED_EVENT_CREATE, "GUILD_SCHEDULED_EVENT_CREATE"},
        {GUILD_SCHEDULED_EVENT_DELETE, "GUILD_SCHEDULED_EVENT_DELETE"},
        {GUILD_SCHEDULED_EVENT_UPDATE, "GUILD_SCHEDULED_EVENT_UPDATE"},
        {GUILD_SCHEDULED_EVENT_USER_REMOVE, "GUILD_SCHEDULED_EVENT_USER_REMOVE"},
        {GUILD_SOUNDBOARD_SOUND_CREATE, "GUILD_SOUNDBOARD_SOUND_CREATE"},
        {GUILD_SOUNDBOARD_SOUND_DELETE, "GUILD_SOUNDBOARD_SOUND_DELETE"},
        {GUILD_SOUNDBOARD_SOUNDS_UPDATE, "GUILD_SOUNDBOARD_SOUNDS_UPDATE"},
        {GUILD_SOUNDBOARD_SOUND_UPDATE, "GUILD_SOUNDBOARD_SOUND_UPDATE"},
        {SOUNDBOARD_SOUNDS, "SOUNDBOARD_SOUNDS"},
        {GUILD_STICKERS_UPDATE, "GUILD_STICKERS_UPDATE"},
        {GUILD_UPDATE, "GUILD_UPDATE"},
        {INTEGRATION_CREATE, "INTEGRATION_CREATE"},
        {INTEGRATION_DELETE, "INTEGRATION_DELETE"},
        {INTEGRATION_UPDATE, "INTEGRATION_UPDATE"},
        {INVITE_CREATE, "INVITE_CREATE"},
        {INVITE_DELETE, "INVITE_DELETE"},
        {MESSAGE_CREATE, "MESSAGE_CREATE"},
        {MESSAGE_DELETE, "MESSAGE_DELETE"},
        {MESSAGE_DELETE_BULK, "MESSAGE_DELETE_BULK"},
        {MESSAGE_POLL_VOTE_ADD, "MESSAGE_POLL_VOTE_ADD"},
        {MESSAGE_POLL_VOTE_REMOVE, "MESSAGE_POLL_VOTE_REMOVE"},
        {MESSAGE_REACTION_ADD, "MESSAGE_REACTION_ADD"},
        {MESSAGE_REACTION_REMOVE, "MESSAGE_REACTION_REMOVE"},
        {MESSAGE_REACTION_REMOVE_ALL, "MESSAGE_REACTION_REMOVE_ALL"},
        {MESSAGE_REACTION_REMOVE_EMOJI, "MESSAGE_REACTION_REMOVE_EMOJI"},
        {MESSAGE_UPDATE, "MESSAGE_UPDATE"},
        {PRESENCE_UPDATE, "PRESENCE_UPDATE"},
        {READY, "READY"},
        {RESUMED, "RESUMED"},
        {STAGE_INSTANCE_CREATE, "STAGE_INSTANCE_CREATE"},
        {STAGE_INSTANCE_DELETE, "STAGE_INSTANCE_DELETE"},
        {SUBSCRIPTION_CREATE, "SUBSCRIPTION_CREATE"},
        {STAGE_INSTANCE_UPDATE, "STAGE_INSTANCE_UPDATE"},
        {SUBSCRIPTION_DELETE, "SUBSCRIPTION_DELETE"},
        {SUBSCRIPTION_UPDATE, "SUBSCRIPTION_UPDATE"},
        {THREAD_CREATE, "THREAD_CREATE"},
        {THREAD_DELETE, "THREAD_DELETE"},
        {THREAD_LIST_SYNC, "THREAD_LIST_SYNC"},
        {THREAD_MEMBERS_UPDATE, "THREAD_MEMBERS_UPDATE"},
        {THREAD_MEMBER_UPDATE, "THREAD_MEMBER_UPDATE"},
        {THREAD_UPDATE, "THREAD_UPDATE"},
        {TYPING_START, "TYPING_START"},
        {USER_UPDATE, "USER_UPDATE"},
        {VOICE_CHANNEL_EFFECT_SEND, "VOICE_CHANNEL_EFFECT_SEND"},
        {VOICE_SERVER_UPDATE, "VOICE_SERVER_UPDATE"},
        {VOICE_STATE_UPDATE, "VOICE_STATE_UPDATE"},
        {WEBHOOKS_UPDATE, "WEBHOOKS_UPDATE"}
    }};
    bool isBot;
    int intents;
    std::string token;
    ix::WebSocket webSocket;
    json payload = { {"op", 1},{"d", nullptr} }, id;
    std::unordered_map<std::string_view, std::function<void(const json&)>> eventHandlers;
    WebSocket& operator=(const WebSocket&) = delete;
    WebSocket(const WebSocket&) = delete;
    WebSocket(const std::string& token, const int& intents, bool& isBot) {
        WebSocket::token = token;
        WebSocket::intents = intents;
        WebSocket::isBot = isBot;
        id = {
            {"op", 2},
            {"d", {
                {"token", token},
                {"intents", intents},
                {"properties", {
                    {"os", "linux"},
                    {"browser", "firefox"},
                    {"device", "firefox"}
                }},
                //{"compress", 1},
                {"presence", {
                    {"activities", json::array({
                        {
                            //{"name", "asdsadsadsadsa"},
                            //{"type", 2}
                        }
                    })},
                    {"status", "idle"},
                    {"since", 91879201},
                    {"afk", false}
                }}
            }}
        };
        ix::initNetSystem();
        webSocket.setUrl("wss://gateway.discord.gg/?v=10&encoding=json");
        webSocket.setHandshakeTimeout(5);
        Logs::create(INFO, WEBSOCKET, "ixwebsocket init");
        webSocket.setOnMessageCallback([this, res = json(), heartbeat_interval = 0, connected = false](const ix::WebSocketMessagePtr& msg) mutable {
            if (msg->type == ix::WebSocketMessageType::Message) {
                res = json::parse(msg->str);
            //#ifdef DEBUG
                Logs::create(INFO, WEBSOCKET, res["op"].dump() + " " + res["t"].dump());
            //#endif
                switch (res["op"].get<int>()) {
                case 10:
                    heartbeat_interval = res["d"]["heartbeat_interval"].get<int>();
                    !connected ? connected = true, webSocket.send(id.dump()) : 0;
                    std::thread([this, &heartbeat_interval, &connected]() {
                        while (connected && heartbeat_interval != -1) {
                            Logs::create(INFO, WEBSOCKET, "Heartbeat " + std::to_string(heartbeat_interval));
                            std::this_thread::sleep_for(milliseconds(heartbeat_interval));
                            webSocket.send(payload.dump());
                        }
                    }).detach();
                    break;
                case 0:
                    if (eventHandlers.find(res["t"].get<std::string>()) != eventHandlers.end()) {
                        eventHandlers[res["t"].get<std::string>()](res);
                    }
                    break;
                }
            }
        });
        webSocket.start();
    }
public:
    /*
    void sendPresenceUpdate(int statusType, const std::string& activityName) {
        json prsUpdate = {
            {"op", 3},
            {"d", {
                {"since", 0},
                {"activities", json::array({{"name", activityName}, {"type", 2}})},
                {"status", statusType == 1 ? "online" : "idle"},
                {"afk", false}
            }}
        };
        webSocket.send(prsUpdate.dump());
    }
    */
    static WebSocket& getInstance(const std::string& token = "", const int intents = 0, bool bot = true) {
        Logs::create(WARNING, WEBSOCKET, "Instance event");
        static WebSocket instance(token, intents, bot);
        return instance;
    }
    ~WebSocket() {
        webSocket.close();
        ix::uninitNetSystem();
    }
    std::string getToken() const {
        return isBot ? std::string("Bot " + WebSocket::token) : WebSocket::token;
    }
    int getIntents() const {
        return WebSocket::intents;
    }
    void on(const int event, std::function<void(const json&)> handler) {
        eventHandlers[events[event].second] = [handler](const json& message) {
            handler(message.get<json>());
        };
    }
    void once(const int event, std::function<void(const json&)> handler) {
        eventHandlers[events[event].second] = [event, handler, isCalled = false](const json& message) mutable {
            isCalled == false ? isCalled = true : 0, handler(message.get<json>());
        };
    }
    void start() {
        while (1) std::this_thread::sleep_for(1ms);
    }
};
#endif