#include "tile/base/net/endpoint.h"

#include "tile/base/byte_order.h"
#include "tile/base/config.h"
#include "tile/base/string.h"

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

#if (TILE_HAVE_GETIFADDRS == 1 || TILE_HAVE_FREEIFADDRS == 1)
#include <ifaddrs.h>
#else
extern "C" {
#include "tile/base/net/detail/android/ifaddrs.h"
}
#endif

namespace tile {
namespace {
constexpr auto kOffsetSunPath = offsetof(sockaddr_un, sun_path);

std::string
SockAddrToString(const sockaddr *addr, socklen_t length)
{
    auto af = addr->sa_family;
    switch (af) {
    case AF_INET: {
        auto p          = reinterpret_cast<const sockaddr_in *>(addr);
        uint32_t s_addr = NetworkToHost32(p->sin_addr.s_addr);
        return Format("{}.{}.{}.{}:{}", (s_addr >> 24) & 0xff, (s_addr >> 16) & 0xff, (s_addr >> 8) & 0xff,
                      s_addr & 0xff, NetworkToHost16(p->sin_port));
    }
    case AF_INET6: {
        auto p = reinterpret_cast<const sockaddr_in6 *>(addr);
        char ip[INET6_ADDRSTRLEN];
        TILE_CHECK(inet_ntop(af, &p->sin6_addr, ip, INET6_ADDRSTRLEN));
        return Format("[{}]:{}", ip, NetworkToHost16(p->sin6_port));
    }
    case AF_UNIX: {
        auto p = reinterpret_cast<const sockaddr_un *>(addr);
        if (p->sun_path[0] == '\0' && p->sun_path[1] != '\0') {
            return Format("@{}", Slice(p->sun_path + 1, length - 1 - kOffsetSunPath));
        } else {
            return std::string(p->sun_path, length - kOffsetSunPath);
        }
    }
    default: {
        return Format("TODO: Endpoint::ToString() for AF #{}.", af);
    }
    }
}
}// namespace

#define INTERNAL_PTR() (*reinterpret_cast<sockaddr_storage **>(&storage_))
#define INTERNAL_CPTR() (*reinterpret_cast<sockaddr_storage *const *>(&storage_))

sockaddr *
EndpointRetriever::RetrieveAddr()
{
    return reinterpret_cast<sockaddr *>(&storage_);
}

socklen_t *
EndpointRetriever::RetrieveLength()
{
    return &length_;
}

Endpoint
EndpointRetriever::Build()
{
    return Endpoint(RetrieveAddr(), length_);
}

Endpoint::Endpoint(const sockaddr *addr, socklen_t length) : length_(length)
{
    if (IsTriviallyCopyable()) {
        memcpy(&storage_, addr, length);
    } else {
        INTERNAL_PTR() = new sockaddr_storage;
        memcpy(INTERNAL_PTR(), addr, length);
    }
}

void
Endpoint::SlowDestroy()
{
    delete INTERNAL_PTR();
}

void
Endpoint::SlowCopyUninitialized(const Endpoint &ep)
{
    length_        = ep.length_;
    INTERNAL_PTR() = new sockaddr_storage;
    memcpy(INTERNAL_PTR(), ep.Get(), ep.length_);
}

void
Endpoint::SlowCopy(const Endpoint &ep)
{
    if (!IsTriviallyCopyable()) { SlowDestroy(); }
    if (ep.IsTriviallyCopyable()) {
        length_ = ep.length_;
        memcpy(&storage_, &ep.storage_, length_);
    } else {
        length_        = ep.length_;
        INTERNAL_PTR() = new sockaddr_storage;
        memcpy(INTERNAL_PTR(), ep.Get(), ep.length_);
    }
}

const sockaddr *
Endpoint::SlowGet() const
{
    return reinterpret_cast<const sockaddr *>(INTERNAL_CPTR());
}

std::string
Endpoint::ToString() const
{
    if (TILE_UNLIKELY(Empty())) { return "(null)"; }

    return SockAddrToString(Get(), Length());
}

bool
operator==(const Endpoint &lhs, const Endpoint &rhs)
{
    return memcmp(lhs.Get(), rhs.Get(), lhs.Length()) == 0;
}

Endpoint
EndpointFromIpv4(const std::string &ip, std::uint16_t port)
{
    EndpointRetriever er;
    auto addr_ptr = er.RetrieveAddr();
    auto p        = reinterpret_cast<sockaddr_in *>(addr_ptr);
    memset(p, 0, sizeof(sockaddr_in));
    TILE_PCHECK(inet_pton(AF_INET, ip.c_str(), &p->sin_addr), "Cannot parse [{}]", ip);
    p->sin_port          = HostToNetwork16(port);
    p->sin_family        = AF_INET;
    *er.RetrieveLength() = sizeof(sockaddr_in);
    return er.Build();
}

Endpoint
EndpointFromIpv6(const std::string &ip, std::uint16_t port)
{
    EndpointRetriever er;
    auto addr_ptr = er.RetrieveAddr();
    auto p        = reinterpret_cast<sockaddr_in6 *>(addr_ptr);
    memset(p, 0, sizeof(sockaddr_in6));
    TILE_PCHECK(inet_pton(AF_INET6, ip.c_str(), &p->sin6_addr), "Cannot parse [{}]", ip);
    p->sin6_port         = HostToNetwork16(port);
    p->sin6_family       = AF_INET6;
    *er.RetrieveLength() = sizeof(sockaddr_in6);
    return er.Build();
}

Endpoint
EndpointFromUnix(const std::string &path)
{
    EndpointRetriever er;

    auto addr_ptr = er.RetrieveAddr();
    auto p        = reinterpret_cast<sockaddr_un *>(addr_ptr);
    memset(p, 0, sizeof(sockaddr_un));
    TILE_PCHECK(path.size() <= sizeof(p->sun_path), "path is too long");
    if (path[0] == '@') {
        p->sun_path[0] = '\0';
        memcpy(p->sun_path + 1, path.data() + 1, path.size() - 1);
    } else {
        memcpy(p->sun_path, path.data(), path.size());
    }
    p->sun_family = AF_UNIX;

    static_assert(kOffsetSunPath == sizeof(sockaddr_un) - sizeof(p->sun_path), "");

    *er.RetrieveLength() = kOffsetSunPath + path.size();

    return er.Build();
}

std::string
EndpointGetIp(const Endpoint &endpoint)
{
    std::string result;
    if (endpoint.Family() == AF_INET) {
        result.resize(INET_ADDRSTRLEN + 1);
        TILE_PCHECK(inet_ntop(endpoint.Family(), &endpoint.UnsafeGet<sockaddr_in>()->sin_addr,
                              const_cast<char *>(result.data()), result.size()));
    } else if (endpoint.Family() == AF_INET6) {
        result.resize(INET6_ADDRSTRLEN + 1);
        TILE_PCHECK(inet_ntop(endpoint.Family(), &endpoint.UnsafeGet<sockaddr_in6>()->sin6_addr,
                              const_cast<char *>(result.data()), result.size()));
    } else {
        TILE_CHECK(0, "Unexpected: Address family #{} is not is valid IP address family.", endpoint.Family());
    }
    return result.substr(0, result.find('\x00'));
}

std::uint16_t
EndpointGetPort(const Endpoint &endpoint)
{
    TILE_CHECK(endpoint.Family() == AF_INET || endpoint.Family() == AF_INET6,
               "Unexpected: Address family #{} is not valid IP address family.", endpoint.Family());
    if (endpoint.Family() == AF_INET) {
        return NetworkToHost16(reinterpret_cast<const sockaddr_in *>(endpoint.Get())->sin_port);
    } else if (endpoint.Family() == AF_INET6) {
        return NetworkToHost16(reinterpret_cast<const sockaddr_in6 *>(endpoint.Get())->sin6_port);
    }
    TILE_NOT_IMPLEMENTED("Unsupported family [{}]", endpoint.Family());
    // crash
    return 0;
}

std::expected<std::vector<Endpoint>, int>
ResolveDomain(const std::string &domain, uint16_t port)
{
    std::vector<Endpoint> ret;
    addrinfo hints, *result;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    auto port_str     = std::to_string(port);
    auto service      = (port != 0) ? port_str.c_str() : nullptr;

    int rt            = getaddrinfo(domain.c_str(), service, &hints, &result);
    if (rt != 0) {
        TILE_LOG_WARNING("getaddrinfo failed: {}", gai_strerror(rt));
        return std::make_unexpected(rt);
    }

    for (auto curr = result; curr != nullptr; curr = curr->ai_next) {
        EndpointRetriever er;
        auto addr = er.RetrieveAddr();
        memcpy(addr, curr->ai_addr, curr->ai_addrlen);
        *er.RetrieveLength() = curr->ai_addrlen;
        ret.emplace_back(er.Build());
    }

    freeaddrinfo(result);
    return ret;
}

std::vector<Endpoint>
GetInterfaceAddresses()
{
    ifaddrs *ifs_raw;
    TILE_PCHECK(getifaddrs(&ifs_raw) == 0, "Cannot enumerate NICs");
    std::unique_ptr<ifaddrs, decltype(&freeifaddrs)> ifs(ifs_raw, &freeifaddrs);

    std::vector<Endpoint> result;

    for (auto current = ifs.get(); current; current = current->ifa_next) {
        auto addr = current->ifa_addr;
        if (!addr) {
            TILE_LOG_WARNING_ONCE("Skipping device [{}] when enumerating interface addresses. No "
                                  "address is assigned to this device",
                                  current->ifa_name);
            continue;
        }

        EndpointRetriever er;
        auto af = addr->sa_family;
#define TEST_AF_AND_COPY(AddressFamily, Storage)                                                                       \
    case AddressFamily: {                                                                                              \
        memcpy(er.RetrieveAddr(), addr, sizeof(Storage));                                                              \
        *er.RetrieveLength() = sizeof(Storage);                                                                        \
        break;                                                                                                         \
    }

        switch (af) {
            TEST_AF_AND_COPY(AF_INET, sockaddr_in)
            TEST_AF_AND_COPY(AF_INET6, sockaddr_in6)
            TEST_AF_AND_COPY(AF_UNIX, sockaddr_in6)
#ifdef AF_PACKET
        case AF_PACKET: {
            continue;
        }
#endif
        default: {
            TILE_LOG_WARNING_ONCE("Unrecognized address family #{} is ignored.", af);
            continue;
        }
        }
        result.push_back(er.Build());
    }

    return result;
}

bool
IsPrivateIpv4AddressRfc(const Endpoint &addr)
{
    // <mask, expected>
    // ip & mask == expected
    static std::pair<std::uint32_t, std::uint32_t> kRanges[] = {
        {0xFF000000, 0x0A000000}, // 10.0.0.0/8
        {0xFFF00000, 0xAC100000}, // 172.16.0.0/12
        {0xFFFF0000, 0xC0A80000}, // 192.168.0.0/16
    };

    if (addr.Family() != AF_INET) { return false; }

    auto ip = NetworkToHost32(addr.UnsafeGet<sockaddr_in>()->sin_addr.s_addr);
    for (auto &&item : kRanges) {
        if ((ip & item.first) == item.second) { return true; }
    }

    return false;
}

bool
IsPrivateIpv4AddressCorp(const Endpoint &addr)
{
    static std::pair<std::uint32_t, std::uint32_t> kRanges[] = {
        {0xFF000000, 0x0A000000}, // 10.0.0.0/8
        {0xFFC00000, 0x64400000}, // 100.64.0.0/10
        {0xFFF00000, 0xAC100000}, // 172.16.0.0/12
        {0xFFFF0000, 0xC0A80000}, // 192.168.0.0/16

        {0xFF000000, 0x09000000}, // 9.0.0.0/8
        {0xFF000000, 0x0B000000}, // 11.0.0.0/8
        {0xFF000000, 0x1E000000}, // 30.0.0.0/8
    };
    if (addr.Family() != AF_INET) { return false; }

    auto ip = NetworkToHost32(addr.UnsafeGet<sockaddr_in>()->sin_addr.s_addr);
    for (auto &&item : kRanges) {
        auto &mask     = item.first;
        auto &expected = item.second;
        if ((ip & mask) == expected) { return true; }
    }
    return false;
}

bool
IsGuaIpv6Address(const Endpoint &addr)
{
    if (addr.Family() != AF_INET6) { return false; }
    auto v6 = addr.UnsafeGet<sockaddr_in6>()->sin6_addr;
    // 2000::/3
    return v6.s6_addr[0] >= 0x20 && v6.s6_addr[0] <= 0x3f;
}

std::ostream &
operator<<(std::ostream &os, const Endpoint &endpoint)
{
    return os << endpoint.ToString();
}

std::optional<Endpoint>
TryParseTraits<Endpoint>::TryParse(Slice s, from_ipv4_t)
{
    // auto s = str.ToSsring();
    auto pos = s.find(':');
    if (pos == Slice::npos) { return {}; }
    auto ip   = s.substr(0, pos).ToString();
    auto port = tile::TryParse<std::uint16_t>(s.substr(pos + 1));
    if (!port) { return {}; }
    EndpointRetriever er;
    auto addr = er.RetrieveAddr();
    auto p    = reinterpret_cast<sockaddr_in *>(addr);
    memset(p, 0, sizeof(sockaddr_in));
    if (inet_pton(AF_INET, ip.c_str(), &p->sin_addr) != 1) { return {}; }
    p->sin_port          = HostToNetwork16(*port);
    p->sin_family        = AF_INET;
    *er.RetrieveLength() = sizeof(sockaddr_in);
    return er.Build();
}

std::optional<Endpoint>
TryParseTraits<Endpoint>::TryParse(Slice s, from_ipv6_t)
{
    auto pos = s.find_last_of(':');
    if (pos == std::string::npos) { return {}; }
    if (pos < 2) { return {}; }
    auto ip   = s.substr(1, pos - 2).ToString();
    auto port = tile::TryParse<std::uint16_t>(s.substr(pos + 1));
    if (!port) { return {}; }
    EndpointRetriever er;
    auto addr = er.RetrieveAddr();
    auto p    = reinterpret_cast<sockaddr_in6 *>(addr);
    memset(p, 0, sizeof(sockaddr_in6));
    if (inet_pton(AF_INET6, ip.c_str(), &p->sin6_addr) != 1) { return {}; }
    p->sin6_port         = HostToNetwork16(*port);
    p->sin6_family       = AF_INET6;
    *er.RetrieveLength() = sizeof(sockaddr_in6);
    return er.Build();
}

std::optional<Endpoint>
TryParseTraits<Endpoint>::TryParse(Slice s, from_unix_t)
{
    EndpointRetriever er;

    auto addr = er.RetrieveAddr();
    auto p    = reinterpret_cast<sockaddr_un *>(addr);
    memset(p, 0, sizeof(sockaddr_un));

    if (s.size() > sizeof(p->sun_path)) { return {}; }

    if (s[0] == '@') {
        p->sun_path[0] = '\0';
        memcpy(p->sun_path + 1, s.data() + 1, s.size() - 1);
    } else {
        memcpy(p->sun_path, s.data(), s.size());
    }
    p->sun_family        = AF_UNIX;

    *er.RetrieveLength() = kOffsetSunPath + s.size();

    return er.Build();
}

std::optional<Endpoint>
TryParseTraits<Endpoint>::TryParse(Slice s)
{
    if (auto opt = TryParse(s, from_ipv4)) {
        return opt;
    } else {
        return TryParse(s, from_ipv6);
    }
}

Endpoint
EndpointFromString(Slice ip_port)
{
    auto opt = TryParse<Endpoint>(ip_port);
    TILE_CHECK(!!opt, "Cannot parse endpoint [{}].", ip_port);
    return *opt;
}

}// namespace tile
