#include "module.h"

#include <balancer/kernel/custom_io/stream.h>
#include <balancer/kernel/helpers/errors.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/log/errorlog.h>
#include <balancer/kernel/module/module.h>

#include <library/cpp/containers/comptrie/comptrie.h>
#include <library/cpp/containers/stack_array/stack_array.h>

#include <util/draft/holder_vector.h>
#include <util/generic/ptr.h>

using namespace NConfig;
using namespace NSrvKernel;

MODULE(prefix_path_router) {
    class TItem : public TModuleParams, public IConfig::IFunc {
    public:
        TItem(const TString& name, const TModuleParams& mp)
            : TModuleParams(mp)
            , Name_(name)
        {
            Config->ForEach(this);

            if (!Slave_) {
                ythrow TConfigParseError() << "no module configured";
            }

            if (!IsDefault() && !Route_) {
                ythrow TConfigParseError() << "no prefix specified";
            }
        }

        bool IsDefault() const noexcept {
            return Name_ == "default";
        }

        const TString& Name() const noexcept {
            return Name_;
        }

        const TString& Route() const noexcept {
            return Route_;
        }

        START_PARSE {
            ON_KEY("route", Route_) {
                return;
            }

            {
                Slave_.Reset(Loader->MustLoad(key, Copy(value->AsSubConfig())).Release());
                return;
            }
        } END_PARSE

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

    private:
        const TString Name_;
        TString Route_;
        THolder<IModule> Slave_;
    };

public:
    TModule(const TModuleParams& mp)
        : TModuleBase(mp)
    {
        Config->ForEach(this);

        if (Items_.empty()) {
            ythrow TConfigParseError() << "no modules configured";
        }

        for (auto i = Items_.begin(); i != Items_.end(); ++i) {
            if ((*i)->IsDefault()) {
                DefaultItem_ = *i;
            } else {
                AddRoute((*i)->Route(), *i);
            }
        }
    }

    START_PARSE {
        ON_KEY("case_insensitive", CaseInsensitive_) {
            return;
        }

        {
            Items_.PushBack(MakeHolder<TItem>(key, Copy(value->AsSubConfig())));
            return;
        }
    } END_PARSE

    TError DoRun(const TConnDescr& descr) const noexcept override {
        TItem* item = Navigate(descr.Request->RequestLine().Path.AsStringBuf());

        if (!item) {
            LOG_ERROR(TLOG_ERR, descr, "no module for request handling");
            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: " << item->Name());
        descr.ExtraAccessLog << ' ' << item->Name();
        return item->Slave()->Run(descr);
    }

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

private:
    void AddRoute(TString route, TItem* item) {
        if (!route.EndsWith('/')) {
            route += '/';
        }

        if (CaseInsensitive_) {
            route.to_lower();
        }

        size_t len = route.size();

        Trie_.Add(route.data(), len, item);

        if (len > MaxEntrySize_) {
            MaxEntrySize_ = len;
        }
    }

    TItem* Navigate(TStringBuf buffer) const noexcept {
        TItem* result = nullptr;

        TString data = TString(buffer.SubStr(0, MaxEntrySize_)) + "/";
        size_t sz = data.Size() - 1;

        if (CaseInsensitive_) {
            for (size_t i = 0; i < sz; ++i) {
                data[i] = tolower(data[i]);
            }
        }

        if (!Trie_.Find(data.Data(), sz + 1, &result)) {
            data[sz] = 0; size_t plen;
            Trie_.FindLongestPrefix(data.Data(), sz, &plen, &result);
        }

        if (result) {
            return result;
        } else {
            return DefaultItem_;
        }
    }

private:
    THolderVector<TItem> Items_;
    TItem* DefaultItem_ = nullptr;
    TCompactTrieBuilder<char, TItem*, TAsIsPacker<TItem*>> Trie_;
    size_t MaxEntrySize_ = 0;
    bool CaseInsensitive_ = false;
};

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