#include "check_message.h"

#include <saas/library/attributes/validate.h>

#include <util/datetime/base.h>
#include <util/system/maxlen.h>

namespace NSaas {
    const ui32 TMessageLimits::MAX_MESSAGE_SIZE_BYTES = 63 * 1024 * 1024;
    const ui32 TMessageLimits::MAX_URL_SIZE = URL_MAX - 2;

    void BaseMessageChecks(const NRTYServer::TMessage& message) {
        Y_ENSURE(message.HasDocument(), "document not set");
        auto& doc = message.GetDocument();
        Y_ENSURE(doc.GetUrl().size() <= TMessageLimits::MAX_URL_SIZE, "Document url is too long");
        Y_ENSURE(doc.HasModificationTimestamp(), "document timestamp not set");
        ui64 now = TInstant::Now().Seconds();
        Y_ENSURE(doc.GetModificationTimestamp() <= now, "document timestamp is more then now (doc_ts="
                        << doc.GetModificationTimestamp()
                        << ", now=" << now << ")");
    }

    void CheckSearchAttributes(const NRTYServer::TMessage::TDocument& doc) {
        for (auto& attr : doc.GetSearchAttributes()) {
            NRTYServer::ValidateSearchAttribute(attr);
        }
    }

    void CheckGroupAttributes(const NRTYServer::TMessage::TDocument& doc) {
        for (auto& attr : doc.GetGroupAttributes()) {
            NRTYServer::ValidateGroupAttribute(attr);
        }
    }

    void TCheckMessageSettings::Init(const TYandexConfig::Section& section) {
        const TYandexConfig::Directives& dir = section.GetDirectives();

        dir.GetValue("Disabled", Disabled);
        dir.GetValue("CheckMaxSizeBytes", CheckMaxSizeBytes);
        dir.GetValue("CheckAttributes", CheckAttributes);
        dir.GetValue("KeyPrefixCheckType", KeyPrefixCheckType);
    }

    void TCheckMessageSettings::ToString(IOutputStream& so) const {
        so << "Disabled: " << Disabled << Endl;
        so << "CheckMaxSizeBytes: " << CheckMaxSizeBytes << Endl;
        so << "CheckAttributes: " << CheckAttributes << Endl;
        so << "KeyPrefixCheckType: " << KeyPrefixCheckType << Endl;
    }

    TCheckMessageSettings& TCheckMessageSettings::SetDisabled(bool value) {
        Disabled = value;
        return *this;
    }

    TCheckMessageSettings& TCheckMessageSettings::SetCheckMaxSizeBytes(ui32 bytes) {
        if (bytes > TMessageLimits::MAX_MESSAGE_SIZE_BYTES) {
            ythrow yexception() << "the requested size (" << bytes << "bytes) is not supported, max size: "
                << TMessageLimits::MAX_MESSAGE_SIZE_BYTES << "bytes";
        }
        CheckMaxSizeBytes = bytes;
        return *this;
    }

    TCheckMessageSettings& TCheckMessageSettings::SetCheckAttributes(bool value) {
        CheckAttributes = value;
        return *this;
    }

    TCheckMessageSettings& TCheckMessageSettings::SetKeyPrefixCheckType(EKeyPrefixCheckType value) {
        KeyPrefixCheckType = value;
        return *this;
    }

    bool TCheckMessageSettings::GetDisabled() const {
        return Disabled;
    }

    ui32 TCheckMessageSettings::GetCheckMaxSizeBytes() const {
        return Disabled ? 0 : CheckMaxSizeBytes;
    }

    bool TCheckMessageSettings::GetCheckAttributes() const {
        return !Disabled && CheckAttributes;
    }

    EKeyPrefixCheckType TCheckMessageSettings::GetKeyPrefixCheckType() const {
        return Disabled ? EKeyPrefixCheckType::any : KeyPrefixCheckType;
    }

    TMessageChecker::TMessageChecker(const TCheckMessageSettings& settings)
        : Settings(settings)
    {}

    bool TMessageChecker::Check(const NRTYServer::TMessage& message) const {
        if (Settings.GetDisabled()) {
            return true;
        }
        BaseMessageChecks(message);
        if (Settings.GetCheckMaxSizeBytes()) {
            Y_ENSURE(message.ByteSizeLong() <= Settings.GetCheckMaxSizeBytes(),
                "message size more then " << Settings.GetCheckMaxSizeBytes() << "bytes"
            );
        }

        auto& doc = message.GetDocument();
        if (Settings.GetCheckAttributes()) {
            CheckSearchAttributes(doc);
            CheckGroupAttributes(doc);
        }
        {
            if (Settings.GetKeyPrefixCheckType() == EKeyPrefixCheckType::zero) {
                Y_ENSURE(doc.GetKeyPrefix() == 0);
            } else if (Settings.GetKeyPrefixCheckType() == EKeyPrefixCheckType::non_zero) {
                Y_ENSURE(doc.GetKeyPrefix() != 0);
            }
        }
        return true;
    }

    bool TMessageChecker::Check(const NRTYServer::TMessage& message, TString& error) const noexcept {
        try {
            return Check(message);
        } catch(...) {
            error = CurrentExceptionMessage();
        }
        return false;
    }

}
