#include "module.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/module/module.h>
#include <balancer/kernel/custom_io/stream.h>
#include <balancer/kernel/net/address.h>
#include <balancer/kernel/process/thread_info.h>

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

#include <util/digest/numeric.h>
#include <util/generic/hash.h>
#include <util/generic/ptr.h>
#include <util/generic/set.h>
#include <util/generic/string.h>
#include <util/generic/vector.h>
#include <util/stream/output.h>
#include <util/string/cast.h>
#include <util/string/vector.h>
#include <util/thread/singleton.h>

#include <utility>

#include <sys/types.h>

using namespace NConfig;
using namespace NSrvKernel;
using namespace NModIpDispatch;

MODULE(ipdispatch) {
private:
    static void CheckName(const TString& name) {
        if (TMatcher(TNameFsm::Instance()).Match(name).Final() != true) {
            Y_WARN_ONCE("section name \"" << name << "\" is wrong, balancer won't start with it soon");
        }
    }


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

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

            CheckName(name);

            if (Ports_.empty()) {
                Ports_.push_back(0);
            }

            if (Hosts_.empty()) {
                Hosts_.push_back("");
            }

            for (const auto& h: Hosts_) {
                for (auto p: Ports_) {
                    AddHostPort(h, p);
                }
            }
        }

        ~TItem() override {
        }

    private:
        START_PARSE {
            STATS_ATTR;

            if (key == "ip") {
                Hosts_.push_back(value->AsString());
                return;
            }

            if (key == "ips") {
                class THostsFiller : public NConfig::IConfig::IFunc {
                public:
                    THostsFiller(TItem& item, NConfig::IConfig* config)
                        : Item_(item)
                    {
                        config->ForEach(this);
                    }

                private:
                    START_PARSE {
                        Item_.Hosts_.push_back(value->AsString());
                        return;
                    } END_PARSE

                private:
                    TItem& Item_;
                } filler(*this, value->AsSubConfig());
                return;
            }

            if (key == "port") {
                Ports_.push_back(FromString<ui16>(value->AsString()));
                return;
            }

            if (key == "ports") {
                class TPortsFiller : public NConfig::IConfig::IFunc {
                public:
                    TPortsFiller(TItem& item, NConfig::IConfig* config)
                        : Item_(item)
                    {
                        config->ForEach(this);
                    }

                private:
                    START_PARSE {
                        Item_.Ports_.push_back(FromString<ui16>(value->AsString()));
                        return;
                    } END_PARSE

                private:
                    TItem& Item_;
                } filler(*this, value->AsSubConfig());
                return;
            }

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

                return;
            }
        } END_PARSE

    public:
        const TAddrDescrs& Descriptors() const noexcept {
            return Descriptors_;
        }

        IModule* Module() const noexcept {
            return Module_.Get();
        }

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

    private:
        void AddHostPort(const TString& host, ui16 port) {
            if (IsDisabledHost(TStringBuilder() << host << ':' << port)) {
                return;
            }
            // find AddrDescrs for current module
            if (host == "*") {
                if (port) {
                    ForEachNonLocalAddress(port, [this] (TNetworkAddress addr) { PushAddr(addr); });
                } else {
                    Descriptors_.push_back(TAddrDescr::MatchAll());
                }
            } else if (host.empty()) {
                if (port) {
                    Descriptors_.push_back(TAddrDescr::MatchPort(port));
                } else {
                    Descriptors_.push_back(TAddrDescr::MatchAll());
                }
            } else {
                const TNetworkAddress addr(host, port);

                PushAddr(addr);
            }
        }

        void PushAddr(const TNetworkAddress& addr) {
            for (auto i = addr.Begin(); i != addr.End(); ++i) {
                Descriptors_.push_back(TAddrDescr(NAddr::TAddrInfo(&*i)));
            }
        }

    private:
        using THosts = TVector<TString>;
        using TPorts = TVector<ui16>;

    private:
        const TString Name_;

        TString StatsAttr_;
        THosts Hosts_;
        TPorts Ports_;

        TAddrDescrs Descriptors_;
        THolder<IModule> Module_;
    };

    using TItemPtr = TIntrusivePtr<TItem>;


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

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

private:
    START_PARSE {
        PARSE_EVENTS;

        AddModule(key, value->AsSubConfig());

        return;
    } END_PARSE

    void AddModule(const TString& key, IConfig* cfg) {
        const TItemPtr item(new TItem(key, Copy(cfg)));
        const TAddrDescrs& descrs = item->Descriptors();

        if (descrs.empty()) {
            DisabledModules_.push_back(std::move(item));
        } else {
            for (const auto& i: descrs) {
                if (Y_UNLIKELY(Modules_.contains(i))) {
                    auto moduleIt = Modules_.find(i);
                    if (moduleIt->second.Get() != item.Get()) {
                        ythrow TConfigParseError()
                            << "collision of addresses' hashes found in ipdispatch section "
                            << key.Quote() << " with " << moduleIt->second->Name().Quote();
                    }
                }

                Modules_[i] = item;
            }
        }
    }

    TError DoRun(const TConnDescr& descr) const noexcept override {
        const TAddrDescr addrDescr = descr.LocalAddr();
        auto module = Modules_.find(addrDescr);

        if (module == Modules_.end()) {
            module = Modules_.find(TAddrDescr::MatchPort(addrDescr.Port));
        }

        // backward configs compability
        if (module == Modules_.end()) {
            module = Modules_.find(TAddrDescr::MatchHost(addrDescr.Host));
        }

        if (module == Modules_.end()) {
            module = Modules_.find(TAddrDescr::MatchAll());
        }

        Y_REQUIRE(module != Modules_.end(),
                  TForceStreamClose{});

        return module->second->Module()->Run(descr);
    }

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

private:
    using TModules = THashMap<TAddrDescr, TItemPtr, TAddrDescrOps, TAddrDescrOps>;
    TModules Modules_;
    TVector<TItemPtr> DisabledModules_;
};

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