#include "module.h"

#include <balancer/kernel/log/errorlog.h>
#include <balancer/kernel/matcher/matcher.h>
#include <balancer/kernel/module/module.h>
#include <util/generic/ptr.h>
#include <util/generic/strbuf.h>
#include <util/generic/string.h>
#include <util/generic/vector.h>

using namespace NConfig;
using namespace NSrvKernel;
using namespace NModRegexp;

namespace {
    bool IsDefault(TStringBuf name) noexcept {
        return name == "default";
    }
}

Y_TLS(regexp) {
    using TPriority = float;

    class TItem {
    public:
        TItem(TString name, TPriority prioirty, IModule* submodule) noexcept
            : Name_(std::move(name))
            , Priority_(prioirty)
            , Slave_(submodule)
            , Hits_(0)
        {}

        TStringBuf Name() const noexcept {
            return Name_;
        }

        TPriority Priority() const noexcept {
            return Priority_;
        }

        IModule* Slave() const noexcept {
            return Slave_;
        }

        size_t Hits() const noexcept {
            return Hits_;
        }

        void Hit() noexcept {
            Hits_++;
        }


    private:
        const TString Name_;
        const TPriority Priority_ = 0.0;
        IModule* Slave_;

        size_t Hits_ = 0;
    };

    void Hit(size_t index) noexcept {
        Items[index].second->Hit();
        const auto greater = [](const auto& l, const auto &r) {
            return l->Priority() > r->Priority() || l->Priority() == r->Priority()  && l->Hits() > r->Hits();
        };
        for (size_t i = index; i > 0 && greater(Items[i].second, Items[i - 1].second) && RandomNumber<ui32>() % 2 == 0; --i) {
            std::swap(Items[i], Items[i - 1]);
        }
    }

    [[nodiscard]] TItem* FindFirstMatch(const TConnDescr& descr) noexcept {
        for (size_t i = 0; i < Items.size(); ++i) {
            const auto& item = Items[i];
            if (item.first->Match(descr)) {
                TItem* ret = item.second.Get();
                Hit(i);
                return ret;
            }
        }
        return nullptr;
    }

    TVector<std::pair<IRequestMatcher*, THolder<TItem>>> Items;
};

MODULE_WITH_TLS(regexp) {
    using TPriority = float;

    struct TConfigParser final : public TModuleParams, public IConfig::IFunc {
        struct TItemParser final : public TModuleParams, public IConfig::IFunc {
            TItemParser(const TModuleParams& mp)
                : TModuleParams(mp)
            {
                Config->ForEach(this);
            }

            struct TItemInfo {
                THolder<IRequestMatcher> Matcher;
                THolder<IModule> Submodule;
                TPriority Priority;
                TString Name;
            };

            TItemInfo Construct(TString name) {
                if (!Submodule) {
                    ythrow TConfigParseError() << "no module configured";
                }

                if (!IsDefault(name) && !Matcher) {
                    ythrow TConfigParseError() << "no matcher configured";
                }

                if (IsDefault(name) && Matcher) {
                    ythrow TConfigParseError() << "default section contains matcher";
                }

                if (Priority < 0.0) {
                    ythrow TConfigParseError() << "negative priority";
                }

                if (IsDefault(name)) {
                    Priority = -1.0;
                    Matcher.Reset(new TAllMatcher<TConnDescr>());
                }
                return TItemInfo{std::move(Matcher), std::move(Submodule), Priority, std::move(name)};
            }

            START_PARSE {
                ON_KEY("priority", Priority) {
                    return;
                }

                if (auto matcher = ConstructRequestMatcher(Control, key, value->AsSubConfig())) {
                    Y_ENSURE_EX(!Matcher, TConfigParseError() << "several matchers");
                    Matcher = std::move(matcher);
                    return;
                }

                {
                    Y_ENSURE_EX(!Submodule, TConfigParseError() << "several modules");
                    Submodule = Loader->MustLoad(key, Copy(value->AsSubConfig()));
                    return;
                }
            } END_PARSE

            TPriority Priority = 0.0;
            THolder<IRequestMatcher> Matcher;
            THolder<IModule> Submodule;
        };

        TConfigParser(const TModuleParams& mp)
            : TModuleParams(mp)
        {
            Config->ForEach(this);
        }

        START_PARSE {
            {
                TItemParser parser(Copy(value->AsSubConfig()));
                auto item = parser.Construct(std::move(key));
                ItemsInfo_.emplace_back(std::move(item));
                return;
            }
        } END_PARSE

        TVector<TItemParser::TItemInfo> ItemsInfo_;
    };

public:
    TModule(const TModuleParams& mp)
        : TModuleBase(mp)
    {
        TConfigParser parser(mp);

        if (parser.ItemsInfo_.empty()) {
            ythrow TConfigParseError() << "no modules configured";
        }
        ItemsInfo_ = std::move(parser.ItemsInfo_);
        StableSortBy(ItemsInfo_, [](const auto& elem) { return -elem.Priority; });
    }

    TError DoRun(const TConnDescr& descr, TTls& tls) const noexcept override {
        TTls::TItem* slave = tls.FindFirstMatch(descr);

        if (!slave) {
            LOG_ERROR(TLOG_ERR, descr, "no module for request handling");
            descr.Properties->ConnStats.BackendError += 1;
            descr.ExtraAccessLog.SetSummary(GetHandle()->Name(), "no matching module");
            return Y_MAKE_ERROR(THttpError{404} << "no module for request handling");
        }

        LOG_ERROR(TLOG_INFO, descr, "found handler: " << slave->Name());
        descr.ExtraAccessLog << ' ' << slave->Name();
        return slave->Slave()->Run(descr);
    }

    bool DoExtraAccessLog() const noexcept override {
        return true;
    }

    bool DoCanWorkWithoutHTTP() const noexcept override {
        return true;
    }

    void DoCheckConstraints() const override {
        for (const auto& item : ItemsInfo_) {
            if (item.Matcher->NeedsHttp()) {
                CheckHasParent("http");
                break;
            }
        }
    }

    THolder<TTls> DoInitTls(IWorkerCtl* process) override {
        auto tls = MakeHolder<TTls>();
        for (const auto& i : ItemsInfo_) {
            tls->Items.emplace_back(i.Matcher.Get(), MakeHolder<TTls::TItem>(i.Name, i.Priority, i.Submodule.Get()));
            tls->Items.back().first->Init(process);
        }
        return tls;
    }

private:
    TVector<TConfigParser::TItemParser::TItemInfo> ItemsInfo_;
};

IModuleHandle* NModRegexp::Handle() {
    return TModule::Handle();
}
