#include "module.h"

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

#include <library/cpp/regex/pire/regexp.h>

#include <util/generic/ptr.h>
#include <util/generic/vector.h>

#include <util/system/compiler.h>

#include <algorithm>
#include <utility>


using namespace NConfig;
using namespace NSrvKernel;
using namespace NModRegexpHost;


MODULE(regexp_host) {
    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() && !Pattern_) {
                ythrow TConfigParseError() << "no matcher configured";
            }

            if (IsDefault()) {
                Priority_ = 0;
            }

            if (Priority_ < 0) {
                ythrow TConfigParseError() << "negative priority";
            }
        }

        TItem(const TItem& rhs) = delete;
        TItem& operator=(const TItem& rhs) = delete;

        TItem(TItem&& rhs) noexcept
            : TModuleParams(std::move(rhs))
            , Priority_(rhs.Priority_)
            , Pattern_(std::move(rhs.Pattern_))
            , Name_(std::move(rhs.Name_))
            , Slave_(std::move(rhs.Slave_))
        {}

        TItem& operator=(TItem&& rhs) noexcept {
            TModuleParams::operator=(std::move(rhs));
            Priority_ = rhs.Priority_;
            Pattern_ = std::move(rhs.Pattern_);
            Name_ = std::move(rhs.Name_);
            Slave_ = std::move(rhs.Slave_);
            return *this;
        }

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

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

        int Priority() const noexcept {
            return Priority_;
        }

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

            ON_KEY("pattern", Pattern_) {
                return;
            }

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

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

                return;
            }
        } END_PARSE

        TFsm BuildFsm() const {
            if (Y_LIKELY(!IsDefault())) {
                // surround does not work after glueing for some reason, write it by hand
                return TFsm(Pattern_, TFsm::TOptions().SetCaseInsensitive(CaseInsensitive_).SetSurround(false));
            }

            ythrow yexception() << "trying to build fsm for default regexp_path section";
        }

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

    private:
        int Priority_ = 1;
        TString Pattern_;
        TString Name_;
        THolder<IModule> Slave_;
        bool CaseInsensitive_{ true };
    };

    using TItems = TVector<TItem>;
    using TProcessingItems = TVector<TItem*>;

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

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

        ProcessingItems_.reserve(Items_.size());

        auto sortFunc = [](const TItem* left, const TItem* right) -> bool {
            // reverse order by priority and forward by address (by addition time)
            return right->Priority() < left->Priority() || (right->Priority() == left->Priority() && left < right); // TODO: maybe compare by prefix len
        };

        for (auto& item: Items_) {
            if (!item.IsDefault()) {
                ProcessingItems_.push_back(&item);
                std::push_heap(ProcessingItems_.begin(), ProcessingItems_.end(), sortFunc);
            } else {
                Default_ = &item;
            }
        }

        std::sort_heap(ProcessingItems_.begin(), ProcessingItems_.end(), sortFunc);
        if (!ProcessingItems_.empty()) {
            Fsm_ = ProcessingItems_[0]->BuildFsm();
            for (size_t i = 1; i < ProcessingItems_.size(); ++i) {
                Fsm_ = Fsm_ | ProcessingItems_[i]->BuildFsm();
            }
        }
    }


    START_PARSE {
        {
            Items_.push_back(TItem(key, Copy(value->AsSubConfig())));

            return;
        }
    } END_PARSE


    TError DoRun(const TConnDescr& descr) const noexcept override {
        Y_ASSERT(descr.Request);

        TItem* slave = Default_;

        if (const auto hostValue = descr.Request->Headers().GetFirstValue("host")) {
            TMatcher matcher{ Fsm_ };
            if (NSrvKernel::Match(matcher, hostValue).Final()) {
                slave = ProcessingItems_[*matcher.MatchedRegexps().first];
            }
        }

        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;
    }

private:
    TItems Items_;
    TProcessingItems ProcessingItems_;
    TFsm Fsm_{ TFsm::False() };
    TItem* Default_ = nullptr;
};

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