#include <webserv/socket/ASocket.hpp>

#include <webserv/log/Log.hpp> // for Log, LOCATION

#include <stdexcept>    // for runtime_error
#include <string>       // for operator+, basic_string, to_string, allocator, char_traits, string
#include <system_error> // for generic_category, system_error
#include <utility>      // for move

#include <errno.h>      // for errno
#include <fcntl.h>      // for fcntl, FD_CLOEXEC, F_GETFD, F_GETFL, O_NONBLOCK, F_SETFD, F_SETFL
#include <sys/socket.h> // for recv, send
#include <unistd.h>     // for close

ASocket::ASocket(int fd, IoState event) : fd_(fd), ioState_(event)
{
    Log::trace(LOCATION);
    if (fd_ == -1)
    {
        Log::error("Invalid file descriptor");
        throw std::runtime_error("Invalid file descriptor");
    }
    setNonBlocking();
}

ASocket::~ASocket()
{
    Log::trace(LOCATION + "Closing socket fd: " + std::to_string(fd_));
    if (fd_ != -1)
    {
        close(fd_);
    }
}

ssize_t ASocket::read(void *buf, size_t len) const
{
    Log::trace(LOCATION);
    ssize_t bytesRead = ::recv(fd_, buf, len, 0);
    if (bytesRead == -1)
    {
        throw std::system_error(errno, std::generic_category(), "Socket: Read error");
    }
    return bytesRead;
}

ssize_t ASocket::write(const void *buf, size_t len) const
{
    Log::trace(LOCATION);
    ssize_t bytesSent = ::send(fd_, buf, len, 0);
    if (bytesSent == -1)
    {
        throw std::system_error(errno, std::generic_category(), "Socket: Write error");
    }
    return bytesSent;
}

void ASocket::setNonBlocking() const
{
    // Set file status flags (O_NONBLOCK)
    int flags = fcntl(fd_, F_GETFL, 0);
    if (flags == -1)
    {
        throw std::system_error(errno, std::generic_category(), "ASocket: Failed to get FD flags");
    }
    if (fcntl(fd_, F_SETFL, flags | O_NONBLOCK) == -1)
    {
        throw std::system_error(errno, std::generic_category(), "ASocket: Failed to set FD non-blocking");
    }

    // Set file descriptor flags (O_CLOEXEC)
    int fdFlags = fcntl(fd_, F_GETFD, 0);
    if (fdFlags == -1)
    {
        throw std::system_error(errno, std::generic_category(), "ASocket: Failed to get FD descriptor flags");
    }
    if (fcntl(fd_, F_SETFD, fdFlags | FD_CLOEXEC) == -1)
    {
        throw std::system_error(errno, std::generic_category(), "ASocket: Failed to set FD close-on-exec");
    }

    // Debug output
    flags = fcntl(fd_, F_GETFL, 0);
    fdFlags = fcntl(fd_, F_GETFD, 0);

    std::string flagStr = "0x" + std::to_string(flags) + " (";
    int mode = flags & 3;
    switch (mode)
    {
    case 0: flagStr += "O_RDONLY "; break;
    case 1: flagStr += "O_WRONLY "; break;
    case 2: flagStr += "O_RDWR "; break;
    default: flagStr += "UNKNOWN_MODE "; break;
    }
    if ((flags & O_NONBLOCK) != 0)
    {
        flagStr += "O_NONBLOCK ";
    }
    if ((fdFlags & FD_CLOEXEC) != 0)
    {
        flagStr += "FD_CLOEXEC ";
    }
    flagStr += ")";

    Log::debug("ASocket: FD " + std::to_string(fd_) + " configured. Flags: " + flagStr);
}

int ASocket::getFd() const noexcept
{
    return fd_;
}

ASocket::IoState ASocket::getEvent() const noexcept
{
    return ioState_;
}

bool ASocket::isDirty() const noexcept
{
    return dirty_;
}

void ASocket::setIOState(IoState event)
{
    if (event == ioState_)
    {
        return;
    }

    Log::debug("Processing state change for socket " + std::to_string(fd_));
    dirty_ = true;
    ioState_ = event;
}

void ASocket::processed()
{
    Log::debug("Socket " + std::to_string(fd_) + " processed");
    dirty_ = false;
}

void ASocket::setFd(int fd)
{
    fd_ = fd;
}

void ASocket::callback() const
{
    if (callback_ != nullptr)
    {
        callback_();
    }
}

void ASocket::setCallback(std::function<void()> callback)
{
    callback_ = std::move(callback);
}
