// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once

#include "infohash.h" // includes socket structures
#include "utils.h"
#include "sockaddr.h"
#include "node_export.h"

#include <list>
#include <map>

namespace dht {

struct Node;
namespace net {
struct Request;
struct Socket;
struct RequestAnswer;
} /* namespace net */

using Tid = uint32_t;
using SocketCb = std::function<void(const Sp<Node>&, net::RequestAnswer&&)>;
struct Socket
{
    Socket() {}
    Socket(SocketCb&& on_receive)
        : on_receive(std::move(on_receive))
    {}
    SocketCb on_receive {};
};

struct Node
{
    const InfoHash id;

    Node(const InfoHash& id, const SockAddr& addr, std::mt19937_64& rd, bool client = false);
    Node(const InfoHash& id, SockAddr&& addr, std::mt19937_64& rd, bool client = false);
    Node(const InfoHash& id, const sockaddr* sa, socklen_t salen, std::mt19937_64& rd)
        : Node(id, SockAddr(sa, salen), rd)
    {}

    const InfoHash& getId() const { return id; }
    const SockAddr& getAddr() const { return addr; }
    std::string getAddrStr() const { return addr.toString(); }
    bool isClient() const { return is_client; }
    bool isIncoming() { return time > reply_time; }

    const time_point& getTime() const { return time; }
    const time_point& getReplyTime() const { return reply_time; }
    void setTime(const time_point& t) { time = t; }

    /**
     * Makes notice about an additionnal authentication error with this node. Up
     * to MAX_AUTH_ERRORS errors are accepted in order to let the node recover.
     * Upon this limit, the node expires.
     */
    void authError()
    {
        if (++auth_errors > MAX_AUTH_ERRORS)
            setExpired();
    }
    void authSuccess() { auth_errors = 0; }

    bool isExpired() const { return expired_; }
    bool isGood(time_point now) const;
    bool isPendingMessage() const;
    size_t getPendingMessageCount() const;

    bool isOld(const time_point& now) const { return time + NODE_EXPIRE_TIME < now; }
    bool isRemovable(const time_point& now) const { return isExpired() and isOld(now); }

    NodeExport exportNode() const
    {
        NodeExport ne;
        ne.id = id;
        ne.addr = addr;
        return ne;
    }
    sa_family_t getFamily() const { return addr.getFamily(); }

    void update(const SockAddr&);

    void requested(const Sp<net::Request>& req);
    void received(time_point now, const Sp<net::Request>& req);
    Sp<net::Request> getRequest(Tid tid);
    void cancelRequest(const Sp<net::Request>& req);

    void setExpired();

    /**
     * Opens a socket on which a node will be able allowed to write for further
     * additionnal updates following the response to a previous request.
     *
     * @param node  The node which will be allowed to write on this socket.
     * @param cb    The callback to execute once updates arrive on the socket.
     *
     * @return the socket.
     */
    Tid openSocket(SocketCb&& cb);

    Sp<Socket> getSocket(Tid id);

    /**
     * Closes a socket so that no further data will be red on that socket.
     *
     * @param socket  The socket to close.
     */
    void closeSocket(Tid id);

    /**
     * Resets the state of the node so it's not expired anymore.
     */
    void reset()
    {
        expired_ = false;
        reply_time = time_point::min();
    }

    /**
     * Generates a new request id, skipping the invalid id.
     *
     * @return the new id.
     */
    Tid getNewTid()
    {
        ++transaction_id;
        return transaction_id ? transaction_id : ++transaction_id;
    }

    std::string toString() const;

    OPENDHT_PUBLIC friend std::ostream& operator<<(std::ostream& s, const Node& h);

    static constexpr const std::chrono::minutes NODE_GOOD_TIME {120};

    /* The time after which we consider a node to be expirable. */
    static constexpr const std::chrono::minutes NODE_EXPIRE_TIME {10};

    /* Time for a request to timeout */
    static constexpr const std::chrono::seconds MAX_RESPONSE_TIME {1};

private:
    /* Number of times we accept authentication errors from this node. */
    static const constexpr unsigned MAX_AUTH_ERRORS {3};

    SockAddr addr;
    bool is_client {false};
    time_point time {time_point::min()};       /* last time eared about */
    time_point reply_time {time_point::min()}; /* time of last correct reply received */
    unsigned auth_errors {0};
    bool expired_ {false};
    Tid transaction_id;
    using TransactionDist = std::uniform_int_distribution<decltype(transaction_id)>;

    std::map<Tid, Sp<net::Request>> requests_ {};
    std::map<Tid, Sp<Socket>> sockets_;
};

} // namespace dht
