#pragma once
#include "Singleton.h"
#include "l_squirrel.h"
#include <array>
#include <asio.hpp>
#include <cstring>
#include <deque>
#include <fstream>
#include <iostream>
#include <memory>
#include <mutex>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <string>
#include <unistd.h>
#include <vector>

extern HSQUIRRELVM v;
extern std::recursive_mutex SqMtx;

using namespace asio;
using asio::ip::tcp;

class Client
{
private:
    struct PendingEvent
    {
        enum class Type
        {
            Connect,
            Message
        };

        Type type = Type::Message;
        std::string payload;
    };

private:
    tcp::resolver resolver;
    tcp::socket socket;
    tcp::resolver::results_type endpoint;
    std::array<char, 8192> buffer;
    std::array<char, 8192000> PackData;
    std::size_t bufferedDataSize = 0;
    std::deque<PendingEvent> pendingEvents;
    std::size_t queuedMessageBytes = 0;
    std::mutex pendingEventsMutex;
    std::string Ip = "192.168.200.27";
    // std::string Ip = "127.0.0.1";
    std::string Port = "65109";

public:
    bool ConnectState = false;

    std::fstream file;

public:
    Client(asio::io_context &io_context, std::string Ip, std::string Port)
        : resolver(io_context),
          socket(io_context)
    {
        endpoint = resolver.resolve(Ip, Port);
        this->Ip = Ip;
        this->Port = Port;
        PackData.fill(0);
    }

private:
    enum : std::size_t
    {
        HeaderSize = 4,
        MaxQueuedMessages = 10000,
        MaxQueuedMessageBytes = 8 * 1024 * 1024
    };

    void ResetPacketBuffer()
    {
        bufferedDataSize = 0;
    }

    void InvokeGatewayConnectCallback()
    {
        std::lock_guard<std::recursive_mutex> lock(SqMtx);
        SQInteger top = sq_gettop(v); // saves the stack size before the call
        sq_pushroottable(v);          // pushes the global table
        sq_pushstring(v, _SC("OnGatewaySocketConnect"), -1);
        if (SQ_SUCCEEDED(sq_get(v, -2)))
        {
            sq_pushroottable(v);            // push the global table as this
            sq_call(v, 1, SQFalse, SQTrue); // calls the function
        }
        sq_settop(v, top); // restores the original stack size
    }

    void InvokeGatewaySocketMsg(const std::string &message)
    {
        std::lock_guard<std::recursive_mutex> lock(SqMtx);
        SQInteger top = sq_gettop(v); // saves the stack size before the call
        sq_pushroottable(v);          // pushes the global table
        sq_pushstring(v, _SC("OnGatewaySocketMsg"), -1);
        if (SQ_SUCCEEDED(sq_get(v, -2)))
        {
            sq_pushroottable(v);
            sq_pushstring(v, message.c_str(), static_cast<SQInteger>(message.size()));
            sq_call(v, 2, SQFalse, SQTrue);
        }
        sq_settop(v, top);
    }

    bool EnqueueConnectEvent()
    {
        std::lock_guard<std::mutex> lock(pendingEventsMutex);
        PendingEvent event;
        event.type = PendingEvent::Type::Connect;
        pendingEvents.push_back(std::move(event));
        return true;
    }

    bool EnqueueMessageEvent(const char *message, std::size_t length)
    {
        std::lock_guard<std::mutex> lock(pendingEventsMutex);
        if (pendingEvents.size() >= MaxQueuedMessages)
        {
            spdlog::error("Gateway pending message queue is full. queued_events={}, limit={}",
                          static_cast<unsigned long long>(pendingEvents.size()),
                          static_cast<unsigned long long>(MaxQueuedMessages));
            return false;
        }

        if ((queuedMessageBytes + length) > MaxQueuedMessageBytes)
        {
            spdlog::error("Gateway pending message bytes exceeded. queued_bytes={}, incoming={}, limit={}",
                          static_cast<unsigned long long>(queuedMessageBytes),
                          static_cast<unsigned long long>(length),
                          static_cast<unsigned long long>(MaxQueuedMessageBytes));
            return false;
        }

        PendingEvent event;
        event.type = PendingEvent::Type::Message;
        event.payload.assign(message, length);
        queuedMessageBytes += length;
        pendingEvents.push_back(std::move(event));
        return true;
    }

    void DrainPendingEvents(std::deque<PendingEvent> &events)
    {
        std::lock_guard<std::mutex> lock(pendingEventsMutex);
        if (pendingEvents.empty())
            return;
        events.swap(pendingEvents);
        queuedMessageBytes = 0;
    }

    bool AppendReceivedData(std::size_t bytes_transferred)
    {
        if (bytes_transferred == 0)
            return true;

        if ((bufferedDataSize + bytes_transferred) > PackData.size())
        {
            spdlog::error("Gateway receive buffer overflow. buffered={}, incoming={}, capacity={}",
                          static_cast<unsigned long long>(bufferedDataSize),
                          static_cast<unsigned long long>(bytes_transferred),
                          static_cast<unsigned long long>(PackData.size()));
            return false;
        }

        std::memcpy(PackData.data() + bufferedDataSize, buffer.data(), bytes_transferred);
        bufferedDataSize += bytes_transferred;
        return true;
    }

    bool ProcessBufferedPackets()
    {
        std::size_t offset = 0;
        while ((bufferedDataSize - offset) >= HeaderSize)
        {
            const int packSize = ByteLittleToInt(reinterpret_cast<unsigned char *>(PackData.data() + offset));
            if (packSize <= 0)
            {
                spdlog::error("Gateway packet length is invalid: {}", packSize);
                return false;
            }

            const std::size_t payloadSize = static_cast<std::size_t>(packSize);
            if (payloadSize > (PackData.size() - HeaderSize))
            {
                spdlog::error("Gateway packet length exceeds buffer. payload={}, capacity={}",
                              static_cast<unsigned long long>(payloadSize),
                              static_cast<unsigned long long>(PackData.size() - HeaderSize));
                return false;
            }

            if ((bufferedDataSize - offset - HeaderSize) < payloadSize)
                break;

            if (!EnqueueMessageEvent(PackData.data() + offset + HeaderSize, payloadSize))
                return false;
            offset += HeaderSize + payloadSize;
        }

        if (offset == 0)
            return true;

        const std::size_t remaining = bufferedDataSize - offset;
        if (remaining > 0)
            std::memmove(PackData.data(), PackData.data() + offset, remaining);
        bufferedDataSize = remaining;
        return true;
    }

public:
    void start()
    {
        async_connect(socket, endpoint, [this](const asio::error_code &error, const tcp::endpoint &connected_endpoint)
                      {
        if (!error) {
            ConnectState = true;
            ResetPacketBuffer();
            EnqueueConnectEvent();
            read();
        } else {
            // std::cerr << "Error connecting to server: " << error.message() << std::endl;
            start();
        } });
    }

    void read()
    {
        socket.async_read_some(asio::buffer(buffer), [this](const asio::error_code &error, std::size_t bytes_transferred)
                               {
        if (!error) {
            if (!AppendReceivedData(bytes_transferred) || !ProcessBufferedPackets()) {
                reconnect();
                return;
            }
            read();
        } else {
            // std::cerr << "Error reading data: " << error.message() << std::endl;
            usleep(10);
            reconnect();
        } });
    }

    void reconnect()
    {
        ConnectState = false;
        ResetPacketBuffer();
        std::cout << "Gateway socket disconnected, reconnecting." << std::endl;
        asio::error_code close_error;
        socket.close(close_error);
        endpoint = resolver.resolve(Ip, Port);
        async_connect(socket, endpoint, [this](const asio::error_code &error, const tcp::endpoint &connected_endpoint)
                      {
            if (!error) {
                ConnectState = true;
                ResetPacketBuffer();
                EnqueueConnectEvent();
                read();
            } else {
                reconnect();
            } });
    }

    void send(unsigned char *message, int Length)
    {
        std::shared_ptr<std::vector<unsigned char> > payload(new std::vector<unsigned char>(message, message + Length));
        async_write(socket, asio::buffer(*payload), [payload](const asio::error_code &error, std::size_t bytes_transferred)
                    {
        if (!error) {
            // std::cout << "Message sent" << std::endl;
        } else {
            std::cerr << "Error sending message: " << error.message() << std::endl;
        } });
    }

    void ClearPack()
    {
        ResetPacketBuffer();
    }

    void PackLogic()
    {
        std::deque<PendingEvent> events;
        DrainPendingEvents(events);
        while (!events.empty())
        {
            PendingEvent event = std::move(events.front());
            events.pop_front();
            if (event.type == PendingEvent::Type::Connect)
            {
                InvokeGatewayConnectCallback();
                continue;
            }

            InvokeGatewaySocketMsg(event.payload);
        }
    }

    static int ByteLittleToInt(unsigned char *Count)
    {
        int int1 = Count[0] & 0xff;
        int int2 = (Count[1] & 0xff) << 8;
        int int3 = (Count[2] & 0xff) << 16;
        int int4 = (Count[3] & 0xff) << 24;

        return int1 | int2 | int3 | int4;
    }
};

class l_socket
{
public:
private:
    l_socket() {}                                   // private constructor to prevent instantiation
    l_socket(const l_socket &) = delete;            // disable copy constructor
    l_socket &operator=(const l_socket &) = delete; // disable assignment operator
    Client *ClientObj = nullptr;

public:
    bool InitState = false;
    static l_socket &getInstance()
    {
        static l_socket instance;
        return instance;
    }

    void InitSqr();
    void InitPackLogic();
    void Init(std::string Ip, std::string Port);
    void Send(const SQChar *Pck);
    void Logic();
    void IntToByteLittle(unsigned char *b, int Count);

public:
};
