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

#include "value.h"
#include "sockaddr.h"

namespace dht {
enum class ImStatus : uint8_t { NONE = 0, TYPING, RECEIVED, READ };
}
MSGPACK_ADD_ENUM(dht::ImStatus)

namespace dht {

class OPENDHT_PUBLIC DhtMessage : public Value::Serializable<DhtMessage>
{
public:
    static const ValueType TYPE;

    DhtMessage(const std::string& s = {}, const Blob& msg = {})
        : service(s)
        , data(msg)
    {}

    std::string getService() const { return service; }

    static Value::Filter getFilter() { return {}; }

    static bool storePolicy(InfoHash key, std::shared_ptr<Value>& value, const InfoHash& from, const SockAddr&);

    static Value::Filter ServiceFilter(const std::string& s);

    /** print value for debugging */
    friend std::ostream& operator<<(std::ostream&, const DhtMessage&);

    std::string service;
    Blob data;
    MSGPACK_DEFINE(service, data)
};

template<typename T>
class SignedValue : public Value::Serializable<T>
{
private:
    using BaseClass = Value::Serializable<T>;

public:
    virtual void unpackValue(const Value& v) override
    {
        if (v.owner) {
            owner = v.owner;
            from = v.owner->getId();
        } else {
            throw std::runtime_error("Value has no owner");
        }
        BaseClass::unpackValue(v);
    }

    static Value::Filter getFilter()
    {
        return [](const Value& v) {
            return v.isSigned();
        };
    }

    Sp<crypto::PublicKey> owner;
    dht::InfoHash from;
};

template<typename T>
class EncryptedValue : public SignedValue<T>
{
public:
    using BaseClass = SignedValue<T>;

public:
    virtual void unpackValue(const Value& v) override
    {
        if (!v.recipient) {
            throw std::runtime_error("Value has no recipient");
        }
        to = v.recipient;
        BaseClass::unpackValue(v);
    }

    static Value::Filter getFilter()
    {
        return Value::Filter::chain(BaseClass::getFilter(),
                                    [](const Value& v) { return static_cast<bool>(v.recipient); });
    }

    dht::InfoHash to;
};

class OPENDHT_PUBLIC ImMessage : public SignedValue<ImMessage>
{
private:
    using BaseClass = SignedValue<ImMessage>;

public:
    static const ValueType TYPE;

    ImMessage() {}
    ImMessage(dht::Value::Id id, std::string&& m, long d = 0)
        : id(id)
        , msg(std::move(m))
        , date(d)
    {}
    ImMessage(dht::Value::Id id, std::string&& dt, std::string&& m, long d = 0)
        : id(id)
        , msg(std::move(m))
        , datatype(std::move(dt))
        , date(d)
    {}
    ImMessage(dht::Value::Id id, std::string&& dt, std::string&& m, std::map<std::string, std::string>&& md, long d = 0)
        : id(id)
        , msg(std::move(m))
        , datatype(std::move(dt))
        , metadatas(std::move(md))
        , date(d)
    {}

    virtual void unpackValue(const Value& v) override
    {
        to = v.recipient;
        SignedValue::unpackValue(v);
    }

    static Value::Filter getFilter() { return SignedValue::getFilter(); }

    dht::InfoHash to;
    dht::Value::Id id {0};
    std::string msg;
    std::string datatype;
    std::map<std::string, std::string> metadatas;
    long date {0};
    ImStatus status {ImStatus::NONE};

    MSGPACK_DEFINE_MAP(id, msg, date, status, datatype, metadatas)
};

class OPENDHT_PUBLIC TrustRequest : public EncryptedValue<TrustRequest>
{
private:
    using BaseClass = EncryptedValue<TrustRequest>;

public:
    static const ValueType TYPE;

    TrustRequest() {}
    TrustRequest(std::string s, std::string ci = {})
        : service(s)
        , conversationId(ci)
    {}
    TrustRequest(std::string s, std::string ci, const Blob& d)
        : service(s)
        , conversationId(ci)
        , payload(d)
    {}

    static Value::Filter getFilter() { return EncryptedValue::getFilter(); }

    std::string service;
    std::string conversationId;
    Blob payload;
    bool confirm {false};
    MSGPACK_DEFINE_MAP(service, conversationId, payload, confirm)
};

class OPENDHT_PUBLIC IceCandidates : public EncryptedValue<IceCandidates>
{
private:
    using BaseClass = EncryptedValue<IceCandidates>;

public:
    static const ValueType TYPE;

    IceCandidates() {}
    IceCandidates(Value::Id msg_id, Blob ice)
        : id(msg_id)
        , ice_data(ice)
    {}

    static Value::Filter getFilter() { return EncryptedValue::getFilter(); }

    template<typename Packer>
    void msgpack_pack(Packer& pk) const
    {
        pk.pack_array(2);
        pk.pack(id);
#if 1
        pk.pack_bin(ice_data.size());
        pk.pack_bin_body((const char*) ice_data.data(), ice_data.size());
#else
        // hack for backward compatibility with old opendht compiled with msgpack 1.0
        // remove when enough people have moved to new versions
        pk.pack_array(ice_data.size());
        for (uint8_t b : ice_data)
            pk.pack(b);
#endif
    }

    virtual void msgpack_unpack(const msgpack::object& o)
    {
        if (o.type != msgpack::type::ARRAY)
            throw msgpack::type_error();
        if (o.via.array.size < 2)
            throw msgpack::type_error();
        id = o.via.array.ptr[0].as<Value::Id>();
        ice_data = unpackBlob(o.via.array.ptr[1]);
    }

    Value::Id id {0};
    Blob ice_data;
};

/* "Peer" announcement
 */
class OPENDHT_PUBLIC IpServiceAnnouncement : public Value::Serializable<IpServiceAnnouncement>
{
private:
    using BaseClass = Value::Serializable<IpServiceAnnouncement>;

public:
    static const ValueType TYPE;

    IpServiceAnnouncement(sa_family_t family = AF_UNSPEC, in_port_t p = 0)
    {
        addr.setFamily(family);
        addr.setPort(p);
    }

    IpServiceAnnouncement(const SockAddr& sa)
        : addr(sa)
    {}

    IpServiceAnnouncement(const Blob& b) { msgpack_unpack(unpackMsg(b).get()); }

    template<typename Packer>
    void msgpack_pack(Packer& pk) const
    {
        pk.pack_bin(addr.getLength());
        pk.pack_bin_body((const char*) addr.get(), addr.getLength());
    }

    virtual void msgpack_unpack(const msgpack::object& o)
    {
        if (o.type == msgpack::type::BIN)
            addr = {(sockaddr*) o.via.bin.ptr, (socklen_t) o.via.bin.size};
        else
            throw msgpack::type_error();
    }

    in_port_t getPort() const { return addr.getPort(); }
    void setPort(in_port_t p) { addr.setPort(p); }

    const SockAddr& getPeerAddr() const { return addr; }

    virtual const ValueType& getType() const { return TYPE; }

    static bool storePolicy(InfoHash, std::shared_ptr<Value>&, const InfoHash&, const SockAddr&);

    /** print value for debugging */
    friend std::ostream& operator<<(std::ostream&, const IpServiceAnnouncement&);

private:
    SockAddr addr;
};

OPENDHT_PUBLIC extern const std::array<std::reference_wrapper<const ValueType>, 5> DEFAULT_TYPES;

OPENDHT_PUBLIC extern const std::array<std::reference_wrapper<const ValueType>, 1> DEFAULT_INSECURE_TYPES;

} // namespace dht
