#include "async_wrapper.h"

#include <balancer/kernel/dns/resolver_config.h>

#include <balancer/kernel/helpers/errors.h>


namespace NSrvKernel::NDns {

TAsyncWrapper::TAsyncWrapper(SocketStateCb socketCb, const TOptions& opts)
    : SocketCb_(socketCb)
{
    Y_VERIFY(SocketCb_); // when socket state changes, ProcessSocket() should be called

    TAresInit::InitOnce();

    ares_options aresOpts;
    Zero(aresOpts);
    int optMask = 0;

    optMask |= ARES_OPT_FLAGS;
    aresOpts.flags = ARES_FLAG_STAYOPEN;

    optMask |= ARES_OPT_TIMEOUTMS;
    aresOpts.timeout = opts.TimeOut.MilliSeconds();

    optMask |= ARES_OPT_TRIES;
    aresOpts.tries = 1;

    optMask |= ARES_OPT_SOCK_STATE_CB;
    aresOpts.sock_state_cb = SocketStateCbInternal;
    aresOpts.sock_state_cb_data = this;

    if (const auto code = ares_init_options(&Channel_, &aresOpts, optMask); code) {
        ythrow TAresError(code) << "failed to init ares channel";
    }
}

TAsyncWrapper::TAsyncWrapper(SocketStateCb socketCb, const TOptions& opts, const TDnsServer& server)
    : TAsyncWrapper(socketCb, opts)
{
    if (!server.IsSet()) {
        return;
    }

    ares_addr_port_node aresServer{};
    aresServer.next     = nullptr;
    aresServer.family   = AF_INET;
    aresServer.udp_port = server.Port;
    aresServer.tcp_port = server.Port;

    if (const auto res = inet_pton(aresServer.family, server.IPv4.c_str(), &aresServer.addr.addr4); res != 1) {
        ythrow TAresError(res) << "failed to convert IPv4 address for DNS server";
    }

    if (const auto res = ares_set_servers_ports(Channel_, &aresServer); res != ARES_SUCCESS) {
        ythrow TAresError(res) << "failed to set DNS server";
    }
}

TAsyncWrapper::~TAsyncWrapper()
{
    CancelRequests();
    ares_destroy(Channel_);
}

void TAsyncWrapper::AsyncResolve(const char* addr, int family, IHostResult* cb) noexcept
{
    ares_gethostbyname(Channel_, addr, family, ResolveHostCbInternal, cb);
}

void TAsyncWrapper::CancelRequests() noexcept
{
    ares_cancel(Channel_);
}

void TAsyncWrapper::ProcessSocket(SOCKET s) noexcept
{
    ares_process_fd(Channel_, s, s);
}

void TAsyncWrapper::ProcessNone() noexcept
{
    ProcessSocket(ARES_SOCKET_BAD);
}

void TAsyncWrapper::SocketStateCbInternal(void* arg, int s, int read, int write) noexcept
{
    auto* wrapper = static_cast<TAsyncWrapper*>(arg);
    Y_VERIFY(wrapper);
    Y_VERIFY(wrapper->SocketCb_);
    wrapper->SocketCb_(s, static_cast<bool>(read), static_cast<bool>(write));
}

void TAsyncWrapper::ResolveHostCbInternal(void* arg, int status, int /*timeouts*/, hostent* he) noexcept
{
    auto* subscriber = static_cast<IHostResult*>(arg);
    Y_VERIFY(subscriber);
    subscriber->OnComplete(status, he);
}

}