#include "saas_push.h"
#include "searchmap_storage.h"

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

#include <library/cpp/balloc/optional/operators.h>

namespace NSaasPush {
    namespace {
        TSearchMapStorage::TServicesByCtype MakeFilter(const TVector<TServiceInfo>& services) {
            TSearchMapStorage::TServicesByCtype res;
            for (const TServiceInfo& serviceInfo: services) {
                res[serviceInfo.Ctype].insert(serviceInfo.Name);
            }
            return res;
        }
    }

    TSearchMapStorage::TImpl::TImpl(const TVector<NSaas::TSearchMapInputSettings>& searchMapSettings, const TServicesByCtype& filter) {
        for (auto&& settings : searchMapSettings) {
            if (SearchMapByCtype.contains(settings.Ctype)) {
                ythrow yexception() << "Two sections of searchmap with same ctype: " << settings.Ctype;
            }
            if (!filter.contains(settings.Ctype)) {
                INFO_LOG << "Getting searchmap for ctype=" << settings.Ctype << " skipped - there are no services with such ctype" << Endl;
                continue;
            }

            INFO_LOG << "Getting searchmap for ctype=" << settings.Ctype << Endl;
            NSearchMapParser::TSearchMap searchMap;
            searchMap = NSaas::GetSearchMap(settings, filter.at(settings.Ctype));
            SearchMapByCtype[settings.Ctype] = searchMap;
        }
    }

    TSearchMapStorage::TSearchMapStorage(const TVector<NSaas::TSearchMapInputSettings>& searchMapSettings, const TVector<TServiceInfo>& services, TDuration updatePeriod)
        : SearchMapSettings(searchMapSettings)
        , ServiceByCtype(MakeFilter(services))
    {
        RecreateImpl();
        for (auto& settings : SearchMapSettings) {
            if (settings.UpdatePeriod != TDuration::Zero()) {
                if (updatePeriod == TDuration::Zero() || updatePeriod > settings.UpdatePeriod) {
                    updatePeriod = settings.UpdatePeriod;
                }
            }
        }
        if (updatePeriod != TDuration::Zero()) {
            Updater = NCron::StartPeriodicJob([this] {
                ThreadDisableBalloc();
                try {
                    INFO_LOG << "Updating SearchMaps..." << Endl;
                    RecreateImpl();
                    INFO_LOG << "SearchMaps updated." << Endl;
                } catch(...) {
                    ERROR_LOG << "Cannot update SearchmapStorage: " << CurrentExceptionMessage() << Endl;
                }
            }, updatePeriod);
        }
    }

    std::optional<NSearchMapParser::TSearchMap> TSearchMapStorage::GetSearchMap(const TString& ctype) const {
        const auto impl = Impl.AtomicLoad();
        if (const auto* map = impl->SearchMapByCtype.FindPtr(ctype)) {
            return *map;
        } else {
            return {};
        }
    }

    void TSearchMapStorage::Stop() {
        Updater.Reset();
    }

    void TSearchMapStorage::AddSubscriber(NSaasPush::TServiceWriter* writer) {
        TGuard<TMutex> g(SubscribersMutex);
        Subscribers.insert(writer);
    }

    void TSearchMapStorage::RemoveSubscriber(NSaasPush::TServiceWriter* writer) {
        TGuard<TMutex> g(SubscribersMutex);
        Subscribers.erase(writer);
    }

    void TSearchMapStorage::RecreateImpl() {
        auto impl = MakeIntrusive<TImpl>(SearchMapSettings, ServiceByCtype);
        Impl.AtomicStore(impl);
        TGuard<TMutex> g(SubscribersMutex);
        for (auto s : Subscribers) {
            auto searchMap = GetSearchMap(s->GetCtype());
            Y_ENSURE(searchMap, "No searchmap for ctype=" << s->GetCtype() << " in storage");
            s->UpdateSearchMap(searchMap.value());
        }
    }

}
