#include "entity.h"

#include <saas/library/persqueue/common/common.h>

#include <util/folder/path.h>
#include <util/string/strip.h>
#include <util/string/vector.h>
#include <util/string/join.h>

#include <google/protobuf/util/message_differencer.h>

#include <algorithm>


namespace NSaasLB {
    TString GetCorrectEntityPath(const TString& originalPath) {
        TFsPath path(originalPath);
        path.Fix();
        if (path.IsAbsolute()) {
            path = path.RelativeTo("/");
        }
        return path.GetPath();
    }

    IEntity::IEntity(const TString& path)
        : Path(GetCorrectEntityPath(path))
    {}

    void IEntity::SetPath(const TString& path) {
        Path = GetCorrectEntityPath(path);
    }

    TString IEntity::GetPath() const {
        return Path;
    }

    NLogBroker::SingleModifyRequest IEntity::GetModifyCommand() const {
        ythrow yexception() << "Not supported modify for " << ToString(GetType());
    }

    bool IEntity::Equal(const IEntity& entity) const {
        return google::protobuf::util::MessageDifferencer::Equals(GetDescribed(), entity.GetDescribed());
    }

    bool IEntity::operator== (const IEntity& entity) const {
        return Equal(entity);
    }

    bool IEntity::operator!= (const IEntity& entity) const {
        return !Equal(entity);
    }

    IEntityWithCodec::IEntityWithCodec(const TString& path, TVector<NPersQueueCommon::ECodec> codecs /*= AllSupportedCodecs*/)
        : IEntity(path)
        , Codecs(codecs.begin(), codecs.end())
    {}

    IEntityWithCodec::IEntityWithCodec(const NLogBroker::DescribePathResult& describe)
        : IEntity(describe.path().path())
    {
        TString codecsStr = describe.has_topic()
            ? GetPropertyValue<TString>(describe.topic().properties().supported_codecs())
            : GetPropertyValue<TString>(describe.consumer().properties().supported_codecs());

        std::transform(codecsStr.begin(), codecsStr.vend(), codecsStr.begin(), ::toupper);
        TVector<TString> codecs = SplitString(codecsStr, ",");
        for (auto& c : codecs) {
            Codecs.insert(FromString<NPersQueueCommon::ECodec>(StripInPlace(c)));
        }
    }

    void IEntityWithCodec::AddCodec(NPersQueueCommon::ECodec codec) {
        Codecs.insert(codec);
    }

    void IEntityWithCodec::RemoveCodec(NPersQueueCommon::ECodec codec) {
        Codecs.erase(codec);
    }

    TVector<NPersQueueCommon::ECodec> IEntityWithCodec::GetCodecs() const {
        return {Codecs.begin(), Codecs.end()};
    }

    TString IEntityWithCodec::GetCodecsString() const {
        TString codecsStr = JoinRange(",", Codecs.begin(), Codecs.end());
        std::transform(codecsStr.begin(), codecsStr.vend(), codecsStr.begin(), ::tolower);
        return codecsStr;
    }

    TConsumer::TConsumer(const TString& path)
        : IEntityWithCodec(path)
    {}

    TConsumer::TConsumer(const NLogBroker::DescribePathResult& describe)
        : IEntityWithCodec(describe)
    {}

    IEntity::TPtr TConsumer::Clone() const {
        return new TConsumer(*this);
    }

    EEntityType TConsumer::GetType() const {
        return EEntityType::consumer;
    }

    NLogBroker::DescribePathResult TConsumer::GetDescribed() const {
        NLogBroker::DescribePathResult described;
        described.mutable_path()->set_path(GetPath());
        described.mutable_consumer();
        FillProperties(*described.mutable_consumer()->mutable_properties());
        return described;
    }

    NLogBroker::SingleModifyRequest TConsumer::GetAddCommand() const {
        NLogBroker::SingleModifyRequest request;
        auto consumer = request.mutable_create_consumer();
        consumer->mutable_path()->set_path(GetPath());
        FillProperties(*consumer->mutable_properties());
        return request;
    }

    NLogBroker::SingleModifyRequest TConsumer::GetRemoveCommand() const {
        NLogBroker::SingleModifyRequest request;
        auto consumer = request.mutable_remove_consumer();
        consumer->mutable_path()->set_path(GetPath());
        return request;
    }

    NLogBroker::SingleModifyRequest TConsumer::GetModifyCommand() const {
        NLogBroker::SingleModifyRequest request;
        auto consumer = request.mutable_update_consumer();
        consumer->mutable_path()->set_path(GetPath());
        FillProperties(*consumer->mutable_properties());
        return request;
    }

    void TConsumer::FillProperties(NLogBroker::ConsumerProperties& props) const {
        IEntityWithCodec::FillProperties(props);
    }

    TTopic::TTopic(
        const TString& path,
        ui32 partitionsCount /*= 1*/,
        TDuration retentionPeriod /*= TDuration::Hours(36)*/
    )
        : IEntityWithCodec(path)
        , PartitionsCount(partitionsCount)
        , RetentionPeriod(retentionPeriod)
    {}

    TTopic::TTopic(const NLogBroker::DescribePathResult& describe)
        : IEntityWithCodec(describe)
    {
        const auto& prop = describe.topic().properties();
        SetPartitionsCount(GetPropertyValue<ui64>(prop.partitions_count()));
        SetRetentionPeriod(TDuration::Seconds(GetPropertyValue<ui64>(prop.retention_period_sec())));
    }

    IEntity::TPtr TTopic::Clone() const {
        return new TTopic(*this);
    }

    void TTopic::SetPartitionsCount(ui32 partitionsCount) {
        PartitionsCount = partitionsCount;
    }

    void TTopic::SetRetentionPeriod(TDuration retentionPeriod) {
        RetentionPeriod = retentionPeriod;
    }

    void TTopic::SetAuthRequired(bool authRequired) {
        AuthRequired = authRequired;
    }

    TDuration TTopic::GetRetentionPeriod() const {
        return RetentionPeriod;
    }

    ui32 TTopic::GetPartitionsCount() const {
        return PartitionsCount;
    }

    bool TTopic::GetAuthRequired() const {
        return AuthRequired;
    }

    EEntityType TTopic::GetType() const {
        return EEntityType::topic;
    }

    NLogBroker::DescribePathResult TTopic::GetDescribed() const {
        NLogBroker::DescribePathResult described;
        described.mutable_path()->set_path(GetPath());
        FillProperties(*described.mutable_topic()->mutable_properties());
        return described;
    }

    NLogBroker::SingleModifyRequest TTopic::GetAddCommand() const {
        NLogBroker::SingleModifyRequest request;
        auto topic = request.mutable_create_topic();
        topic->mutable_path()->set_path(GetPath());
        FillProperties(*topic->mutable_properties());
        return request;
    }

    NLogBroker::SingleModifyRequest TTopic::GetRemoveCommand() const {
        NLogBroker::SingleModifyRequest request;
        auto topic = request.mutable_remove_topic();
        topic->mutable_path()->set_path(GetPath());
        return request;
    }

    NLogBroker::SingleModifyRequest TTopic::GetModifyCommand() const {
        NLogBroker::SingleModifyRequest request;
        auto topic = request.mutable_update_topic();
        topic->mutable_path()->set_path(GetPath());
        FillProperties(*topic->mutable_properties());
        return request;
    }

    void TTopic::FillProperties(NLogBroker::TopicProperties& props) const {
        props.mutable_partitions_count()->set_user_defined(PartitionsCount);
        props.mutable_retention_period_sec()->set_user_defined(RetentionPeriod.Seconds());
        props.mutable_allow_unauthenticated_write()->set_user_defined(false);
        if (AuthRequired) {
            props.mutable_allow_unauthenticated_read()->set_user_defined(false);
        }
        IEntityWithCodec::FillProperties(props);
    }

    TDirectory::TDirectory(const ::NLogBroker::DescribePathResult& describe)
        : IEntity(describe.path().path())
    {}

    IEntity::TPtr TDirectory::Clone() const {
        return new TDirectory(*this);
    }

    EEntityType TDirectory::GetType() const {
        return EEntityType::directory;
    }

    NLogBroker::DescribePathResult TDirectory::GetDescribed() const {
        NLogBroker::DescribePathResult described;
        described.mutable_path()->set_path(GetPath());
        described.mutable_directory();
        return described;
    }

    NLogBroker::SingleModifyRequest TDirectory::GetAddCommand() const {
        NLogBroker::SingleModifyRequest request;
        auto directory = request.mutable_create_directory();
        directory->mutable_path()->set_path(GetPath());
        return request;
    }

    NLogBroker::SingleModifyRequest TDirectory::GetRemoveCommand() const {
        NLogBroker::SingleModifyRequest request;
        auto directory = request.mutable_remove_directory();
        directory->mutable_path()->set_path(GetPath());
        return request;
    }

    TAccount::TAccount(const ::NLogBroker::DescribePathResult& describe)
        : IEntity(describe.path().path())
    {}

    IEntity::TPtr TAccount::Clone() const {
        return new TAccount(*this);
    }

    EEntityType TAccount::GetType() const {
        return EEntityType::account;
    }

    NLogBroker::DescribePathResult TAccount::GetDescribed() const {
        NLogBroker::DescribePathResult described;
        described.mutable_path()->set_path(GetPath());
        described.mutable_account();
        return described;
    }

    NLogBroker::SingleModifyRequest TAccount::GetAddCommand() const {
        ythrow yexception() << "it is impossible to create an account, only admins can perform this operation";
    }

    NLogBroker::SingleModifyRequest TAccount::GetRemoveCommand() const {
        ythrow yexception() << "it is impossible to remove an account, only admins can perform this operation";
    }

}
