#ifndef TLS_NETWORK_HPP_
#define TLS_NETWORK_HPP_
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <memory>
#include <iostream>
#include <string>
#include <net/if.h>
#include <netdb.h>
#include <utils/types.hpp>
#include <gateway/Websocket.hpp>
using std::cout;
using std::cerr;
using std::endl;
class NetworkManager {
private:
    WebSocket& web;
    SSL_CTX* ctx;
    int sock = -1;
    std::unique_ptr<SSL, decltype(&SSL_free)> ssl = { nullptr, &SSL_free };
    void handleSSLInitErrors() {
        ERR_print_errors_fp(stderr);
        abort();
    }
    void createSSLConnection(const std::string& hostname, const std::string& port) {
        addrinfo hints = { 0 }, *res = { 0 };
        hints.ai_socktype = SOCK_STREAM;
        if (getaddrinfo(hostname.c_str(), port.c_str(), &hints, &res) != 0) {
            cerr << "Failed to get address info for " << hostname << ":" << port << endl;
            handleSSLInitErrors();
        }
        sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (sock == -1) {
            cerr << "Failed to create socket" << endl;
            handleSSLInitErrors();
        }
        if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
            cerr << "Failed to connect to " << hostname << ":" << port << endl;
            close(sock);
            handleSSLInitErrors();
        }
        ssl.reset(SSL_new(ctx));
        if (!ssl) {
            cerr << "Failed to create SSL structure" << endl;
            close(sock);
            handleSSLInitErrors();
        }
        SSL_set_fd(ssl.get(), sock);
        if (SSL_connect(ssl.get()) != 1) {
            cerr << "SSL connection failed" << endl;
            close(sock);
            handleSSLInitErrors();
        }
        freeaddrinfo(res);
    }
    NetworkManager& operator=(const NetworkManager&) = delete;
    NetworkManager(const NetworkManager&) = delete;
    NetworkManager() : ctx(nullptr), web(WebSocket::getInstance()) {
        if (!ctx) {
            ctx = SSL_CTX_new(TLS_client_method());
            if (!ctx) {
                cerr << "Failed to create SSL context" << endl;
                handleSSLInitErrors();
            }
            OPENSSL_init_ssl(0, 0);
            OPENSSL_add_all_algorithms_noconf();
            SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
            SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION);
            createSSLConnection("discord.com", "443");
        }
    }
    std::string parseJson(const std::string& response) {
        unsigned long jsonStart = response.find("\r\n\r\n");
        if (jsonStart == std::string::npos) {
            return "";
        }
        jsonStart += 4;
        std::string jsonString = response.substr(jsonStart);
        unsigned long jsonBegin = jsonString.find('['), jsonEnd = jsonString.rfind(']');
        if (jsonBegin == std::string::npos || jsonEnd == std::string::npos || jsonEnd < jsonBegin) {
            return "";
        }
        jsonString = jsonString.substr(jsonBegin, jsonEnd - jsonBegin + 1);
        jsonString.erase(std::remove(jsonString.begin(), jsonString.end(), '\r'), jsonString.end());
        jsonString.erase(std::remove(jsonString.begin(), jsonString.end(), '\t'), jsonString.end());
        jsonString.erase(std::remove(jsonString.begin(), jsonString.end(), '\n'), jsonString.end());
        jsonString.erase(std::remove(jsonString.begin(), jsonString.end(), '\\'), jsonString.end());
        jsonString.erase(std::remove(jsonString.begin(), jsonString.end(), '\''), jsonString.end());
        jsonString.erase(std::remove(jsonString.begin(), jsonString.end(), ' '), jsonString.end());
        try {
            nlohmann::json jsonData = nlohmann::json::parse(jsonString);
            return jsonData.dump();
        }
        catch (const nlohmann::json::parse_error& e) {
            return "";
        }
    }
public:
    static NetworkManager& getInstance() {
        static NetworkManager instance;
        return instance;
    }
    ~NetworkManager() {
        if (ctx) SSL_CTX_free(ctx);
        close(sock);
    }
    std::string request(const std::string& method, const std::string& path, const std::string& data = "") {
    #ifdef DEBUG
        createSSLConnection("discord.com", "443");
    #endif
        std::string result;
        auto net = [this, method, path, data, &result]() mutable {
            std::string request = method + " " + path + " HTTP/1.1\r\nHost: discord.com\r\nAccept: application/json\r\nContent-Type: application/json\r\n";
            request += "Authorization: " + web.getToken() + "\r\nContent-Length: " + std::to_string(data.length()) + "\r\n";
        #ifdef DEBUG
            request += "Connection: close\r\n\r\n";
        #elif defined(RELEASE)
            request += "Connection: keep-alive\r\n\r\n";
        #endif
            request += data;
            if (SSL_write(ssl.get(), request.c_str(), request.length()) <= 0) {
                std::cerr << "Failed to send request" << std::endl;
                handleSSLInitErrors();
            }
        #ifdef DEBUG
            std::vector<char> buffer(1024);
            int bytesRead;
            while ((bytesRead = SSL_read(ssl.get(), buffer.data(), buffer.size())) > 0) {
                result.append(buffer.data(), bytesRead);
                if (bytesRead == buffer.size()) {
                    buffer.resize(buffer.size() * 2);
                }
            }
        #endif
        };
        net();
    #ifdef DEBUG
        return parseJson(result);
    #elif defined(RELEASE)
        return result;
    #endif
    }
};
#endif