/*******************************************************************************
Copyright (c) 2014, Jan Koester jan.koester@gmx.net
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#include <iostream>
#include <algorithm>
#include <chrono>
#include <memory>
#include <vector>
#include <atomic>
#include <thread>
#include <shared_mutex>
#include <unordered_map>

#include <unistd.h>
#include <signal.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <cstring>

#include "config.h"
#include "socket.h"
#include "exception.h"
#include "eventapi.h"
#include "connection.h"
#include "../posix/error.h"

namespace netplus {

    static std::vector<socket*> SSOCKETS;
    static std::unordered_map<int, std::shared_ptr<con>> CONNECTIONS;

    static std::shared_mutex POLL_HANDLER_MUTEX;

    // ------------------------------------------------------------
    // Utility: safe close (no deadlock, no double close)
    // ------------------------------------------------------------
    static void safeCloseSocket(std::shared_ptr<con>& c) {
        if (!c) return;
        if (!c->slots[0].csock) return;

        try {
            c->slots[0].csock->close();
        } catch (...) {}
    }

    // ------------------------------------------------------------
    // pollapi interface
    // ------------------------------------------------------------
    class pollapi {
    public:
        pollapi(eventapi* evcon, int timeout) : evconnection(evcon), _Timeout(timeout) {}
        virtual ~pollapi() = default;

        enum EventHandlerStatus { EVWAIT=0, EVIN=1, EVOUT=2, EVUP=3, EVERR=4, EVCON=-1 };

        virtual void initEventHandler()=0;
        virtual const char* getpolltype()=0;
        virtual int pollState(int pos)=0;
        virtual void setpollEventsFd(int fd, int events)=0;

        virtual int waitEventHandler(int timeout)=0;
        virtual void ConnectEventHandler(int tid, ULONG_PTR args)=0;
        virtual void IoEventHandler(int fd, int events, const int tid, ULONG_PTR args)=0;
        virtual void CloseEventHandler(int fd, int tid, ULONG_PTR args)=0;

    protected:
        eventapi* evconnection;
        int _Timeout;
    };

    // RAII Rearm (EPOLLONESHOT) - manages re-arming on scope exit
    class EpollArmGuard {
    public:
        EpollArmGuard(int fd, std::shared_ptr<con> c, pollapi* pptr)
            : _fd(fd), _con(std::move(c)), _pptr(pptr) {}

        ~EpollArmGuard() {
            if (!_armed) return;
            if (!_con || !_con->slots[0].csock) return;

            int next = EPOLLRDHUP | EPOLLIN | EPOLLONESHOT;

            // Check if we have pending writes
            if (_con->SendData.pos < _con->SendData.size() || _con->slots[0].csock->hasPendingWrite()
                || _con->slots[0].ResponsePending.exchange(false, std::memory_order_acq_rel))
                next |= EPOLLOUT;

            try {
                _pptr->setpollEventsFd(_fd, next);
            } catch (...) {
                // ignore
            }
        }

        void disarm() { _armed = false; }

    private:
        bool _armed = true;
        int _fd;
        std::shared_ptr<con> _con;
        pollapi* _pptr;
    };

    // ------------------------------------------------------------
    // poll implementation
    // ------------------------------------------------------------
    class poll : public pollapi {
    public:
        poll(socket* serversocket, eventapi* evcon, int pollfd, int timeout)
            : pollapi(evcon, timeout),
              _pollFD(pollfd),
              _Events(new epoll_event[serversocket->getMaxconnections()]),
              _ServerSocket(serversocket)
        {
            {
                std::unique_lock<std::shared_mutex> lk(POLL_HANDLER_MUTEX);
                SSOCKETS.emplace_back(_ServerSocket);
            }

            for (int i = 0; i < _ServerSocket->getMaxconnections(); ++i) {
                _Events[i].events = -1;
                _Events[i].data.fd = _ServerSocket->fd();
            }
        }

        const char* getpolltype() override { return "EPOLL"; }
        void initEventHandler() override {}

        int getEventFlags(int pos) { return _Events[pos].events; }
        int getEventFd(int pos) { return _Events[pos].data.fd; }

        std::shared_ptr<con> getConByFd(int fd) {
            std::shared_lock<std::shared_mutex> lk(POLL_HANDLER_MUTEX);
            auto it = CONNECTIONS.find(fd);
            if (it == CONNECTIONS.end()) return nullptr;
            return it->second;
        }

        void setpollEventsFd(int fd, int events) override {
            epoll_event ev{};
            ev.events = events;
            ev.data.fd = fd;

            if (epoll_ctl(_pollFD, EPOLL_CTL_MOD, fd, &ev) < 0) {
                // If FD doesn't exist in epoll, add it instead
                if (errno == ENOENT) {
                    if (epoll_ctl(_pollFD, EPOLL_CTL_ADD, fd, &ev) < 0) {
                        NetException except;
                        except[NetException::Error] << "setpollEventsFd: epoll_ctl ADD failed: " << strerror(errno);
                        throw except;
                    }
                } else {
                    NetException except;
                    except[NetException::Error] << "setpollEventsFd: epoll_ctl MOD failed: " << strerror(errno);
                    throw except;
                }
            }
        }

        int pollState(int pos) override {
            if (_Events[pos].data.fd == _ServerSocket->fd())
                return EVCON;
            return _Events[pos].events;
        }

        int waitEventHandler(int timeout) override {
            int evn = epoll_wait(_pollFD, _Events.get(), _ServerSocket->getMaxconnections(), timeout);
            if (evn < 0) {
                NetException e;
                char str[255];
                strerror_r_netplus(errno, str, 255);
                e[NetException::Error] << "waitEventHandler: epoll_wait failed: " << str;
                throw e;
            }
            return evn;
        }

        // ------------------------------------------------------------
        // Accept new connection
        // ------------------------------------------------------------
        void ConnectEventHandler(const int tid, ULONG_PTR args) override {
            try {
                std::shared_ptr<con> ccon;
                evconnection->CreateConnection(ccon);
                if (!ccon) return;

                if (_ServerSocket->_Type == sockettype::TCP)
                    ccon->slots[0].csock = std::make_unique<tcp>(-1);
                else if (_ServerSocket->_Type == sockettype::SSL) {
                    ssl* srv = static_cast<ssl*>(_ServerSocket);
                    ccon->slots[0].csock = std::make_unique<ssl>(*srv->_tls.cert_map, -1);
                } else if (_ServerSocket->_Type == sockettype::QUIC) {
                    // QUIC: Process ONE UDP datagram per epoll event.
                    // The server socket uses level-triggered EPOLLIN,
                    // so epoll_wait fires again immediately if more
                    // datagrams are queued.  This lets the event loop
                    // interleave other work (e.g. PING ACK processing)
                    // between datagrams.
                    {
                        std::unique_ptr<socket> quic_csock;
                        _ServerSocket->accept(quic_csock, true);
                    }
                    return;
                }

                _ServerSocket->accept(ccon->slots[0].csock, true);
                if (!ccon || !ccon->slots[0].csock) return;

            register_connection:
                // accept4(SOCK_NONBLOCK) already sets non-blocking in tcp::accept
                int fd = ccon->slots[0].csock->fd();

                // insert into connection map
                {
                    std::unique_lock<std::shared_mutex> lk(POLL_HANDLER_MUTEX);
                    CONNECTIONS.emplace(fd, ccon);
                }

                // Serialize connection setup with I/O for this fd
                {
                    std::lock_guard<std::recursive_mutex> lk(ccon->event_mutex);

                    epoll_event setevent{};
                    setevent.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
                    setevent.data.fd = fd;

                    epoll_ctl(_pollFD, EPOLL_CTL_ADD, fd, &setevent);

                    evconnection->ConnectEvent(*ccon, tid, args);

                    // With EPOLLONESHOT, socket must be re-armed after each event.
                    // The batched send loop reduces re-arm syscalls by draining data
                    // in a single epoll event rather than one chunk per event.
                    if (ccon->SendData.pos < ccon->SendData.size() || ccon->slots[0].csock->hasPendingWrite()) {
                        epoll_event ev{};
                        ev.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLONESHOT;
                        ev.data.fd = fd;
                        epoll_ctl(_pollFD, EPOLL_CTL_MOD, fd, &ev);
                    }

                    // Drop local reference under the same lock to serialize refcount updates
                    ccon.reset();
                }

            } catch (NetException& e) {
                if (e.getErrorType() == NetException::Note)
                    return;
                throw;
            }
        }

        // ------------------------------------------------------------
        // IO handling (unified for TCP/SSL/QUIC)
        // ------------------------------------------------------------
        void IoEventHandler(int fd, int events, const int tid, ULONG_PTR args) override {
            std::shared_ptr<con> c = getConByFd(fd);
            if (!c || !c->slots[0].csock) {
                return;
            }

            bool needClose = false;
            // Pre-allocate buffer outside the inner loops to avoid
            // repeated heap allocation on every recv iteration.
            buffer recvBuf(BLOCKSIZE);

            bool doClose = false;
            {
                std::lock_guard<std::recursive_mutex> lk(c->event_mutex);
                if (!c->slots[0].csock) return;

                EpollArmGuard rearm(fd, c, this);

                try {
                    // Peer closed?
                    if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
                        needClose = true;
                    }
                    else {
                        // Step 1: EPOLLIN - buffer transport data
                        // Use a dedicated buffer so recvBuf.size stays
                        // at BLOCKSIZE for the application-data loop.
                        if ((events & EPOLLIN) && !needClose) {
                            try {
                                buffer primeBuf(BLOCKSIZE);
                                c->slots[0].csock->prime_recv(primeBuf, 0);
                            } catch (NetException& e) {
                                if (e.getErrorType() != NetException::Note) throw;
                            }
                        }

                        // Step 2: Handshake (if not done)
                        // Keep driving the handshake state machine until:
                        // - Handshake completes, or
                        // - We need to wait for more data (Note exception)
                        while (!c->slots[0].csock->getHandshakeDone() && !needClose) {
                            // First flush any pending writes
                            if (c->slots[0].csock->hasPendingWrite()) {
                                try {
                                    c->slots[0].csock->flush_out();
                                } catch (NetException& e) {
                                    if (e.getErrorType() == NetException::Note) {
                                        rearm.disarm();
                                        setpollEventsFd(fd, EPOLLOUT | EPOLLRDHUP | EPOLLONESHOT);
                                        return;
                                    }
                                    throw;
                                }
                            }

                            // Drive the handshake state machine
                            // Use handshake_after_connect() for client sockets,
                            // handshake_after_accept() for server sockets
                            try {
                                c->slots[0].csock->handshake_after_accept();
                            } catch (NetException& e) {
                                if (e.getErrorType() != NetException::Note) throw;
                                // Note means need more data or write blocked - exit loop
                                break;
                            }
                        }

                        // Guard will re-arm on return

                        // Post-handshake: if the socket has a framing
                        // callback, invoke it with stream_id=0 to produce
                        // initial connection data (e.g. HTTP/2 SETTINGS).
                        // This breaks the deadlock where the browser waits
                        // for SETTINGS and the server waits for the browser.
                        // IMPORTANT: Only run AFTER handshake completes.
                        // Running during handshake adds data to SendData
                        // that can't be sent yet (sendData throws Note),
                        // which causes the EpollArmGuard to add EPOLLOUT,
                        // creating a busy-loop that starves all workers.
                        if (c->slots[0].csock->getHandshakeDone()) {
                            if (c->slots[0].csock->getSocketType() == sockettype::SSL) {
                                auto *sslsock = static_cast<ssl*>(c->slots[0].csock.get());
                                const auto &fcb = sslsock->getFramingCallback();
                                if (fcb && c->SendData.pos >= c->SendData.size()) {
                                    std::string init = fcb(sslsock->getSelectedAlpn(), 0, "", "");
                                    if (!init.empty()) {
                                        c->SendData.append(init.data(), init.size());
                                    }
                                }
                            }
                        }

                        // Step 3: Application data (handshake done)
                        // Drain all available TLS records to avoid stalling
                        // when multiple records arrive in a single TCP segment.
                        bool hasAppData = (events & EPOLLIN) ||
                                          c->slots[0].csock->hasBufferedData();
                        while (hasAppData && c->slots[0].csock->getHandshakeDone() && !needClose) {
                            size_t rcv = 0;
                            bool wouldBlock = false;

                            // Reset buffer size — recvData/prime_recv
                            // shrink data.size to the actual bytes returned,
                            // which would throttle subsequent reads.
                            recvBuf.size = BLOCKSIZE;

                            try {
                                rcv = c->slots[0].csock->recvData(recvBuf, 0);
                            } catch (NetException& e) {
                                if (e.getErrorType() == NetException::Note) {
                                    wouldBlock = true;
                                } else {
                                    throw;
                                }
                            }

                            if (!wouldBlock && rcv == 0) {
                                // True EOF — peer closed the connection
                                needClose = true;
                            } else if (rcv > 0) {
                                c->RecvData.append(recvBuf.data.buf, rcv);
                                evconnection->RequestEvent(*c, tid, args);
                            }

                            if (wouldBlock) {
                                // The TLS layer may hold a partial record
                                // in rx_tcp_buf but can't decrypt it yet.
                                // Pull more raw TCP data so the record can
                                // be completed without waiting for EPOLLIN
                                // (which won't fire if the kernel buffer is
                                // already drained by a previous prime_recv).
                                bool gotMore = false;
                                try {
                                    buffer prBuf(BLOCKSIZE);
                                    size_t pr = c->slots[0].csock->prime_recv(prBuf, 0);
                                    if (pr > 0) gotMore = true;
                                } catch (NetException&) {
                                    // EAGAIN / would-block — no more kernel data
                                }
                                if (!gotMore) break;
                                continue;  // retry recvData with the new raw bytes
                            }

                            // Continue only if there are more buffered records
                            if (rcv == 0 || !c->slots[0].csock->hasBufferedData())
                                break;
                        }

                        // Step 4: Flush and send
                        // Always attempt to send if there is data queued
                        // (browsers require the server's HTTP/2 SETTINGS before
                        //  sending their request, so we must not wait for EPOLLOUT)
                        if ((c->SendData.pos < c->SendData.size() || c->slots[0].csock->hasPendingWrite()) && !needClose) {
                            if (c->slots[0].csock->hasPendingWrite()) {
                                try {
                                    c->slots[0].csock->flush_out();
                                } catch (NetException& e) {
                                    if (e.getErrorType() == NetException::Note) {
                                        rearm.disarm();
                                        setpollEventsFd(fd, EPOLLOUT | EPOLLRDHUP | EPOLLONESHOT);
                                        return;
                                    }
                                    throw;
                                }
                            }

                            // Send loop: drain all available data in one event
                            // This significantly improves throughput for large files by
                            // reducing epoll_ctl system call overhead
                            bool sendBlockedWouldBlock = false;
                            while (c->SendData.pos < c->SendData.size() && !sendBlockedWouldBlock && !needClose) {
                                size_t remaining = c->SendData.size() - c->SendData.pos;
                                size_t sendlen = std::min((size_t)BLOCKSIZE, remaining);
                                buffer out(c->SendData.data() + c->SendData.pos, sendlen);

                                size_t consumed = 0;
                                try {
                                    consumed = c->slots[0].csock->sendData(out, 0);
                                    // Advance pos immediately after sendData —
                                    // the plaintext is already encrypted and
                                    // queued in the TLS send_queue.  If the
                                    // flush_out below throws EAGAIN we must not
                                    // re-encrypt this chunk on the next cycle.
                                    if (consumed > 0) {
                                        c->SendData.pos += consumed;
                                        if (c->SendData.pos >= c->SendData.size()) {
                                            c->SendData.clear();
                                            c->SendData.pos = 0;
                                        } else if (c->SendData.pos > BLOCKSIZE * 4) {
                                            c->SendData.erase(c->SendData.begin(), c->SendData.begin() + c->SendData.pos);
                                            c->SendData.pos = 0;
                                        }
                                    } else {
                                        break;
                                    }
                                    if (c->slots[0].csock->hasPendingWrite())
                                        c->slots[0].csock->flush_out();
                                } catch (NetException& e) {
                                    if (e.getErrorType() == NetException::Note) {
                                        sendBlockedWouldBlock = true;
                                        break;
                                    }
                                    throw;
                                }
                            }

                            // Send blocked, guard will re-arm with EPOLLOUT
                            if (sendBlockedWouldBlock && c->SendData.pos < c->SendData.size()) {
                                return;
                            }

                            if (c->SendData.pos >= c->SendData.size() && !c->slots[0].csock->hasPendingWrite()) {
                                c->SendData.clear();
                                c->SendData.pos = 0;
                                evconnection->ResponseEvent(*c, tid, args);
                            }
                        } else if (events & EPOLLOUT) {
                            // EPOLLOUT with no data queued — the guard armed
                            // this because ResponsePending was set (and cleared
                            // it via exchange).  Call ResponseEvent so the app
                            // can produce more streaming data.
                            evconnection->ResponseEvent(*c, tid, args);
                        }
                    }

                } catch (NetException& e) {
                    if (e.getErrorType() == NetException::Note) {
                        // Guard will re-arm on return
                        return;
                    }
                    needClose = true;
                } catch (...) {
                    needClose = true;
                }
                if (needClose) {
                    rearm.disarm();
                    doClose = true;
                }

                // Drop local reference under the same lock to serialize refcount updates
                c.reset();
            } // unlock event_mutex

            if (doClose) {
                CloseEventHandler(fd, tid, args);
            }
        }

        // ------------------------------------------------------------
        // Close handler (safe + no deadlock)
        // ------------------------------------------------------------
        void CloseEventHandler(int fd, const int tid, ULONG_PTR args) override {
            std::shared_ptr<con> ccon;

            // take out of map first
            {
                std::unique_lock<std::shared_mutex> lock(POLL_HANDLER_MUTEX);
                auto it = CONNECTIONS.find(fd);
                if (it == CONNECTIONS.end())
                    return;
                ccon = std::move(it->second);
                CONNECTIONS.erase(it);
            }

            if (!ccon) return;

            // now lock per connection
            {
                std::lock_guard<std::recursive_mutex> lk(ccon->event_mutex);

                // remove from epoll
                epoll_ctl(_pollFD, EPOLL_CTL_DEL, fd, nullptr);

                // close socket
                safeCloseSocket(ccon);
            }

            try {
                evconnection->DisconnectEvent(*ccon, tid, args);
            } catch (...) {
                // ignore user callback failures
            }
        }

    private:
        int _pollFD;
        std::unique_ptr<epoll_event[]> _Events;
        socket* _ServerSocket;
    };

    // ------------------------------------------------------------
    // Thread worker
    // ------------------------------------------------------------
    class EventWorkerArgs {
    public:
        int timeout;
        eventapi* event;
        std::map<int,socket*> ssocket;
    };

    class EventWorker {
    public:
        EventWorker(int tid, ULONG_PTR args, EventWorkerArgs* eargs) {
            std::vector<std::thread> local_threads;

            for(auto& kv : eargs->ssocket){
                local_threads.emplace_back([&, tid, args, kv] {
                    try {
                        poll pollptr(kv.second, eargs->event, kv.first, eargs->timeout);

                        while (event::Running) {
                            try {
                                int wait = pollptr.waitEventHandler(eargs->timeout);

                                if (wait == 0) {
                                    // epoll_wait already blocked for the timeout
                                    // duration — no need for additional sleep.
                                    continue;
                                }

                                for (int i = 0; i < wait; ++i) {
                                    // Per-event try/catch: an exception in one
                                    // event must not skip remaining events in
                                    // the batch. With EPOLLONESHOT, skipped
                                    // events leave fds permanently disabled.
                                    try {
                                        int evtype = pollptr.pollState(i);

                                        if (evtype == pollapi::EVCON) {
                                            pollptr.ConnectEventHandler(tid, args);
                                            continue;
                                        }

                                        int fd = pollptr.getEventFd(i);
                                        int flags = pollptr.getEventFlags(i);

                                        pollptr.IoEventHandler(fd, flags, tid, args);
                                    } catch (NetException& e) {
                                        std::cerr << "Event error: " << e.what() << std::endl;
                                    } catch (std::exception& e) {
                                        std::cerr << "Event error: " << e.what() << std::endl;
                                    } catch (...) {
                                        std::cerr << "Event error: unknown exception" << std::endl;
                                    }
                                }
                            } catch (NetException& e) {
                                std::cerr << "Poll Error: " << e.what() << std::endl;
                                // brief pause after error to avoid busy-loop
                                std::this_thread::sleep_for(std::chrono::milliseconds(10));
                            }
                        }
                    } catch (NetException& e) {
                        std::cerr << "Worker thread " << tid << " exited with exception: " << e.what() << std::endl;
                    } catch (...) {
                        std::cerr << "Worker thread " << tid << " exited with unknown exception." << std::endl;
                    }
                });
            }
 
            for (auto& t : local_threads)
                t.join();
        }
    };

    struct ThreadHelperArgs {
        EventWorkerArgs* eargs;
        ULONG_PTR args;
        int tid;
    };

    static void* worker_wrapper(void* data) {
        ThreadHelperArgs* helper_args = reinterpret_cast<ThreadHelperArgs*>(data);

        try {
            EventWorker(helper_args->tid, helper_args->args, helper_args->eargs);
        } catch (NetException& e) {
            std::cerr << "Thread " << helper_args->tid << " exited with exception: " << e.what() << std::endl;
        } catch (...) {
            std::cerr << "Thread " << helper_args->tid << " exited with unknown exception." << std::endl;
        }

        delete helper_args;
        return nullptr;
    }

    // ------------------------------------------------------------
    // eventapi dummies
    // ------------------------------------------------------------
    void eventapi::RequestEvent(con&, const int, ULONG_PTR) {}
    void eventapi::ResponseEvent(con&, const int, ULONG_PTR) {}
    void eventapi::ConnectEvent(con&, const int, ULONG_PTR) {}
    void eventapi::DisconnectEvent(con&, const int, ULONG_PTR) {}

    // ------------------------------------------------------------
    // Signal handler — MUST be async-signal-safe.
    //
    // Acquiring mutexes here deadlocks if the signal is
    // delivered to a thread that already holds the lock.
    // Only set the atomic flag; cleanup happens in
    // runEventloop() after all threads have joined.
    // ------------------------------------------------------------
    static void signalHandler(int signum) {
        if (signum != SIGINT)
            return;
        event::Running = false;
    }

    // Called from runEventloop() AFTER all worker threads have
    // joined — safe to acquire any lock.
    static void cleanupConnections() {
        std::vector<socket*> servers;
        std::vector<std::shared_ptr<con>> conns;

        {
            std::unique_lock<std::shared_mutex> lk(POLL_HANDLER_MUTEX);

            servers = SSOCKETS;
            for (auto& kv : CONNECTIONS)
                conns.push_back(kv.second);

            CONNECTIONS.clear();
        }

        for (auto* s : servers) {
            try { s->close(); } catch (...) {}
        }

        for (auto& c : conns) {
            if (!c) continue;
            std::lock_guard<std::recursive_mutex> lk(c->event_mutex);
            safeCloseSocket(c);
        }
    }

    // ------------------------------------------------------------
    // event class
    // ------------------------------------------------------------
    event::event(std::vector<socket*> serversockets, int timeout)
        : _ServerSockets(serversockets), _Timeout(timeout)
    {
        if (_ServerSockets.empty()) {
            NetException e;
            e[NetException::Critical] << "server socket empty!";
            throw e;
        }

        for(auto* s : _ServerSockets) {
            if (!s) {
                continue;
            }
            s->bind();
            
            
            s->setNonBlock();
            
            
            s->listen();
            
            
            s->setTimeout(timeout);
        }

        threads = sysconf(_SC_NPROCESSORS_ONLN);
    }

    event::event(const event& src) : _ServerSockets(src._ServerSockets), _Timeout(src._Timeout) {}
    event::~event() {}

    void event::runEventloop(ULONG_PTR args) {
        signal(SIGINT, signalHandler);
        signal(SIGPIPE, SIG_IGN);
        
        EventWorkerArgs eargs;

    MAINWORKERLOOP:
        for(auto ss : _ServerSockets) {           
            _pollFD = epoll_create1(0);
            if (_pollFD < 0) {
                NetException e;
                e[NetException::Critical] << "runEventloop: can't create epoll";
                throw e;
            }

            epoll_event setevent{};
            setevent.events = EPOLLIN;
            setevent.data.fd = ss->fd();

            if (epoll_ctl(_pollFD, EPOLL_CTL_ADD, ss->fd(), &setevent) < 0) {
                NetException e;
                e[NetException::Critical] << "runEventloop: epoll_ctl ADD server failed";
                throw e;
            }

            eargs.ssocket[_pollFD] = ss;
        }

        eargs.timeout = _Timeout;
        eargs.event = this;

        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE);

        std::vector<pthread_t> threadpool;
        threadpool.reserve(threads);

        for (int i = 0; i < threads; ++i) {
            ThreadHelperArgs* helper_args = new ThreadHelperArgs{ &eargs, args, i };

            pthread_t tid;
            if (pthread_create(&tid, &attr, worker_wrapper, helper_args) != 0) {
                delete helper_args;
                NetException e;
                e[NetException::Critical] << "runEventloop: pthread_create failed";
                throw e;
            }
            threadpool.push_back(tid);
        }

        pthread_attr_destroy(&attr);

        for (auto& t : threadpool)
            pthread_join(t, nullptr);

        // All worker threads have exited — safe to clean up.
        cleanupConnections();

        // Close all epoll FDs (not just the last one)
        for (auto& kv : eargs.ssocket)
            close(kv.first);
        eargs.ssocket.clear();

        if (event::Restart) {
            event::Restart = false;
            goto MAINWORKERLOOP;
        }
    }

    // ================================================================
    // socketwait – epoll-based implementation
    // ================================================================
    socketwait::socketwait() {
        _handle = ::epoll_create1(EPOLL_CLOEXEC);
        if (_handle < 0) {
            NetException e;
            e[NetException::Error] << "socketwait: epoll_create1 failed: " << strerror(errno);
            throw e;
        }
    }

    socketwait::~socketwait() {
        if (_handle >= 0) ::close(_handle);
    }

    bool socketwait::waitRead(socket &sock, int timeout_ms) {
        SOCKET fd = sock.fd();
        struct epoll_event ev{};
        ev.events  = EPOLLIN;
        ev.data.fd = fd;
        epoll_ctl(_handle, EPOLL_CTL_ADD, fd, &ev);
        struct epoll_event out;
        int r = ::epoll_wait(_handle, &out, 1, timeout_ms);
        epoll_ctl(_handle, EPOLL_CTL_DEL, fd, nullptr);
        return r > 0 && (out.events & (EPOLLIN | EPOLLHUP | EPOLLERR));
    }

    bool socketwait::waitWrite(socket &sock, int timeout_ms) {
        SOCKET fd = sock.fd();
        struct epoll_event ev{};
        ev.events  = EPOLLOUT;
        ev.data.fd = fd;
        epoll_ctl(_handle, EPOLL_CTL_ADD, fd, &ev);
        struct epoll_event out;
        int r = ::epoll_wait(_handle, &out, 1, timeout_ms);
        epoll_ctl(_handle, EPOLL_CTL_DEL, fd, nullptr);
        return r > 0 && (out.events & EPOLLOUT);
    }

    bool socketwait::isDisconnected(socket &sock) {
        SOCKET fd = sock.fd();
        struct epoll_event ev{};
        ev.events  = EPOLLIN;
        ev.data.fd = fd;
        epoll_ctl(_handle, EPOLL_CTL_ADD, fd, &ev);
        struct epoll_event out;
        int r = ::epoll_wait(_handle, &out, 1, 0);
        epoll_ctl(_handle, EPOLL_CTL_DEL, fd, nullptr);
        return r > 0 && (out.events & (EPOLLHUP | EPOLLERR));
    }

} // namespace netplus
