#pragma once

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

#include <library/cpp/digest/old_crc/crc.h>

#include <util/digest/numeric.h>
#include <util/generic/singleton.h>
#include <util/generic/vector.h>
#include <util/network/address.h>
#include <util/network/ip.h>
#include <util/random/random.h>
#include <util/stream/mem.h>

#include <functional>
#include <utility>

namespace NSrvKernel {
    using IRemoteAddrSimpleRef = TSimpleSharedPtr<NAddr::IRemoteAddr>;

    using TRawIpV6 = std::pair<ui64, ui64>;

    inline bool ParseIPv4(NAddr::TOpaqueAddr* addr, const char* str) noexcept {
        sockaddr_in* sa = (sockaddr_in*)addr->Addr();

        if (inet_pton(AF_INET, str, &sa->sin_addr) > 0) {
            sa->sin_family = AF_INET;
            *addr->LenPtr() = sizeof(*sa);

            return true;
        }

        return false;
    }

    inline bool ParseIPv6(NAddr::TOpaqueAddr* addr, const char* str) noexcept {
        sockaddr_in6* sa = (sockaddr_in6*)addr->Addr();

        if (inet_pton(AF_INET6, str, &sa->sin6_addr) > 0) {
            sa->sin6_family = AF_INET6;
            *addr->LenPtr() = sizeof(*sa);

            return true;
        }

        return false;
    }

    inline bool Parse(NAddr::TOpaqueAddr* addr, TStringBuf str) noexcept {
        char buf[128];

        buf[str.copy(buf, sizeof(buf) - 1)] = 0;

        if (ParseIPv4(addr, buf)) {
            return true;
        }

        return ParseIPv6(addr, buf);
    }


    class TRandomAddr: public NAddr::IRemoteAddr {
    public:
        TRandomAddr() noexcept
            : Addr_(RandomNumber<TIpHost>(), RandomNumber<TIpPort>())
        {}

        sockaddr* Addr() const noexcept override {
            return const_cast<sockaddr*>(reinterpret_cast<const sockaddr*>(&Addr_));
        }

        socklen_t Len() const noexcept override {
            return sizeof(Addr_);
        }

    private:
        TIpAddress Addr_;
    };

    class TDummyAddr: public NAddr::IRemoteAddr, public TWithDefaultInstance<TDummyAddr> {
    public:
        TDummyAddr() noexcept
            : Addr_(TIpHost(0), TIpPort(0))
        {}

        sockaddr* Addr() const noexcept override {
            return const_cast<sockaddr*>(reinterpret_cast<const sockaddr*>(&Addr_));
        }

        socklen_t Len() const noexcept override {
            return sizeof(Addr_);
        }

    private:
        TIpAddress Addr_;
    };

    class TAddrHolder {
    public:
        explicit TAddrHolder(const NAddr::IRemoteAddr* addr) noexcept
            : Addr_{addr}
        {}

        const NAddr::IRemoteAddr* Addr() const noexcept {
            return Addr_;
        }

        ui16 Port() const noexcept {
            switch (Addr_->Addr()->sa_family) {
            case AF_INET:
                return ntohs(((const sockaddr_in*)(Addr_->Addr()))->sin_port);
            case AF_INET6:
                return ntohs(((const sockaddr_in6*)(Addr_->Addr()))->sin6_port);
            default:
                return 0;
            }
        }

        TString AddrStr() const noexcept {
            if (ValidCachedHost_) {
                return CachedHost_;
            } else if (Y_UNLIKELY(Addr_)) {
                CachedHost_ = NAddr::PrintHost(*Addr_);
                ValidCachedHost_ = true;
            }

            return CachedHost_;
        }

        TString AddrPortStr() const noexcept {
            if (ValidCachedHostPort_) {
                return CachedHostPort_;
            } else if (Y_LIKELY(Addr_)) {
                CachedHostPort_ = NAddr::PrintHostAndPort(*Addr_);
                ValidCachedHostPort_ = true;
            }

            return CachedHostPort_;
        }

    private:
        mutable TString CachedHost_;
        mutable TString CachedHostPort_;
        const NAddr::IRemoteAddr* Addr_ = nullptr;
        mutable bool ValidCachedHost_ = false;
        mutable bool ValidCachedHostPort_ = false;
    };

    struct TAddrDescr {
        TAddrDescr() noexcept {
        }

        TAddrDescr(ui64 host, ui16 port) noexcept
            : Host(host)
            , Port(port)
        {}

        TAddrDescr(const struct sockaddr* const sa) noexcept {
            switch (sa->sa_family) {
                case AF_INET: {
                    const TIpAddress a(*reinterpret_cast<const sockaddr_in*>(sa));

                    Host = a.Host();
                    Port = a.Port();

                    break;
                }

                case AF_INET6: {
                    const sockaddr_in6* const a = reinterpret_cast<const sockaddr_in6*>(sa);

                    Host = Crc<ui64>(&a->sin6_addr.s6_addr, sizeof(a->sin6_addr.s6_addr));
                    Port = InetToHost(a->sin6_port);

                    break;
                }
            }
        }

        TAddrDescr(const struct sockaddr* const sa, ui16 port) noexcept {
            switch (sa->sa_family) {
                case AF_INET: {
                    const TIpAddress a(*reinterpret_cast<const sockaddr_in*>(sa));

                    Host = a.Host();
                    Port = port;

                    break;
                }

                case AF_INET6: {
                    const sockaddr_in6* const a = reinterpret_cast<const sockaddr_in6*>(sa);

                    Host = Crc<ui64>(&a->sin6_addr.s6_addr, sizeof(a->sin6_addr.s6_addr));
                    Port = port;

                    break;
                }
            }
        }

        TAddrDescr(const NAddr::IRemoteAddr& addr) noexcept
            : TAddrDescr(addr.Addr())
        {
        }

        static TAddrDescr MatchAll() noexcept {
            return {};
        }

        static TAddrDescr MatchPort(ui16 port) noexcept {
            return {static_cast<ui64>(0), port};
        }

        static TAddrDescr MatchHost(ui64 host) noexcept {
            return {host, 0};
        }


        ui64 Host = 0;
        ui16 Port = 0;
    };

    struct TAddrDescrOps {
        bool operator()(const TAddrDescr& l, const TAddrDescr& r) const noexcept {
            return l.Host == r.Host && l.Port == r.Port;
        }

        size_t operator()(const TAddrDescr& d) const noexcept {
            return CombineHashes<size_t>(d.Port, d.Host);
        }
    };

    using TAddrDescrs = TVector<TAddrDescr>;

    const size_t HOSTLENBUF = 256;

    static inline TStringBuf FormatHost(const NAddr::IRemoteAddr* addr, char* buf, size_t len) noexcept {
        TMemoryOutput out(buf, len);

        PrintHost(out, *addr);

        return {buf, out.Buf()};
    }


    /// \brief TNetworAddress for all addresses except loopback (for binding)
    /// \details iterates over all interfaces to bind on each address but loopback's interface,
    /// IPv6-linklocal addresses and 127.0.0.1 and ::1. May throw in case of duplicate addresses on different
    /// interfaces. See also /// https://st.yandex-team.ru/BALANCER-650 , https://st.yandex-team.ru/BALANCER-659
    void ForEachNonLocalAddress(ui16 port, std::function<void(TNetworkAddress)> reaction) /* throws */;

    /// \brief TAddrDescr for all addresses except loopback (for ipdispatch)
    /// \details iterates over all interfaces to bind on each address but loopback's interface,
    /// IPv6-linklocal addresses and 127.0.0.1 and ::1. Silently skips duplicate addresses on different
    /// interfaces. See also /// https://st.yandex-team.ru/BALANCER-650 , https://st.yandex-team.ru/BALANCER-659
    void ForEachNonLocalAddress(ui16 port, std::function<void(TAddrDescr)> reaction) /* throws */;


    ui64 IpHash(const NAddr::IRemoteAddr& addr) noexcept;

    bool HasConflictingAddresses(const TVector<TNetworkAddress>& a, const TVector<TNetworkAddress>& b);
}
