#pragma once

#include <QByteArray>
#include <QList>
#include <QMetaType>
#include <QString>
#include <QStringList>

#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>

#include <QHostAddress>

class QHostInfo
{
public:
    enum HostInfoError {
        NoError = 0,
        HostNotFound = 1,
        UnknownError = 2,
    };

    QHostInfo() = default;

    explicit QHostInfo(const QString &hostName)
        : m_hostName(hostName)
    {
    }

    static QString localHostName()
    {
        char buffer[256] = {};
        if (gethostname(buffer, sizeof(buffer)) == 0) {
            buffer[sizeof(buffer) - 1] = '\0';
            if (buffer[0] != '\0') {
                return QString::fromUtf8(buffer);
            }
        }

        return QStringLiteral("redox");
    }

    static QHostInfo fromName(const QString &hostName)
    {
        QHostInfo info(hostName);

        const QHostAddress literalAddress(hostName);
        if (!literalAddress.isNull()) {
            info.setAddresses({literalAddress});
            info.setError(NoError);
            info.setErrorString(QString());
            return info;
        }

        addrinfo hints = {};
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;

        addrinfo *results = nullptr;
        const QByteArray hostNameUtf8 = hostName.toUtf8();
        const int lookupResult = getaddrinfo(hostNameUtf8.constData(), nullptr, &hints, &results);

        if (lookupResult != 0) {
            info.setError(lookupResult == EAI_NONAME ? HostNotFound : UnknownError);
            info.setErrorString(QString::fromUtf8(gai_strerror(lookupResult)));
            return info;
        }

        QList<QHostAddress> addresses;
        QStringList seenAddresses;

        for (const addrinfo *entry = results; entry != nullptr; entry = entry->ai_next) {
            const void *rawAddress = nullptr;
            int family = AF_UNSPEC;

            switch (entry->ai_family) {
            case AF_INET:
                rawAddress = &reinterpret_cast<const sockaddr_in *>(entry->ai_addr)->sin_addr;
                family = AF_INET;
                break;
            case AF_INET6:
                rawAddress = &reinterpret_cast<const sockaddr_in6 *>(entry->ai_addr)->sin6_addr;
                family = AF_INET6;
                break;
            default:
                continue;
            }

            char buffer[INET6_ADDRSTRLEN] = {};
            if (inet_ntop(family, rawAddress, buffer, sizeof(buffer)) == nullptr) {
                continue;
            }

            const QString addressText = QString::fromUtf8(buffer);
            if (addressText.isEmpty() || seenAddresses.contains(addressText)) {
                continue;
            }

            seenAddresses.append(addressText);
            addresses.append(QHostAddress(addressText));
        }

        freeaddrinfo(results);

        if (addresses.isEmpty()) {
            info.setError(HostNotFound);
            info.setErrorString(QStringLiteral("Host lookup returned no usable addresses"));
            return info;
        }

        info.setAddresses(addresses);
        info.setError(NoError);
        info.setErrorString(QString());
        return info;
    }

    static void abortHostLookup(int)
    {
    }

    void setHostName(const QString &hostName)
    {
        m_hostName = hostName;
    }

    QString hostName() const
    {
        return m_hostName;
    }

    void setAddresses(const QList<QHostAddress> &addresses)
    {
        m_addresses = addresses;
    }

    QList<QHostAddress> addresses() const
    {
        return m_addresses;
    }

    void setError(HostInfoError error)
    {
        m_error = error;
    }

    HostInfoError error() const
    {
        return m_error;
    }

    void setErrorString(const QString &errorString)
    {
        m_errorString = errorString;
    }

    QString errorString() const
    {
        return m_errorString;
    }

    void setLookupId(int lookupId)
    {
        m_lookupId = lookupId;
    }

    int lookupId() const
    {
        return m_lookupId;
    }

private:
    QString m_hostName;
    QList<QHostAddress> m_addresses;
    HostInfoError m_error = UnknownError;
    QString m_errorString;
    int m_lookupId = -1;
};

Q_DECLARE_METATYPE(QHostInfo)
