#include "module.h"
#include "utils.h"

#include <balancer/kernel/fs/watched_state.h>
#include <balancer/kernel/http/parser/request_builder.h>
#include <balancer/kernel/log/errorlog.h>
#include <balancer/kernel/module/iface.h>
#include <balancer/kernel/module/module.h>
#include <balancer/kernel/requester/requester.h>

#include <util/string/builder.h>
#include <util/string/join.h>

using namespace NSrvKernel;

namespace NModFlagsGetter {
    const TStringBuf ResultHeader = "x-yandex-exphandler-result";
    const TStringBuf ServiceHeader = "x-yandex-exphandler";

    namespace {
        TSharedCounter GenStatsCounter(TSharedStatsManager& stats, TStringBuf serviceName, TStringBuf counterName) {
            return stats.MakeCounter({"flags_getter", serviceName, counterName}).Build();
        }
    }
}

using namespace NModFlagsGetter;

namespace {
struct TSharedCountersHolders {
    explicit TSharedCountersHolders(TSharedStatsManager& statsManager, const TString& serviceName)
        : NoAnswer(GenStatsCounter(statsManager, serviceName, "no_answer"))
        , NoHeader(GenStatsCounter(statsManager, serviceName, "no_header"))
        , Disabled(GenStatsCounter(statsManager, serviceName, "disabled"))
        , Success(GenStatsCounter(statsManager, serviceName, "success"))
    {}

    TSharedCounter NoAnswer;
    TSharedCounter NoHeader;
    TSharedCounter Disabled;
    TSharedCounter Success;
};
}

Y_TLS(flags_getter) {
    TTls(TSharedCountersHolders& holders, IWorkerCtl* worker)
        : NoAnswer(holders.NoAnswer, worker->WorkerId())
        , NoHeader(holders.NoHeader, worker->WorkerId())
        , Disabled(holders.Disabled, worker->WorkerId())
        , Success(holders.Success, worker->WorkerId())
    {}

    bool DoesKillSwitchExist() const {
        return KillSwitch.Exists();
    }

    TSharedFileExistsChecker KillSwitch;

    TSharedCounter NoAnswer;
    TSharedCounter NoHeader;
    TSharedCounter Disabled;
    TSharedCounter Success;
};

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

        Y_ENSURE_EX(Submodule_,
            TConfigParseError() << "no submodule in flags_getter");

        Y_ENSURE_EX(FlagsModule_,
            TConfigParseError() << "no flags submodule in flags_getter");

        Y_ENSURE_EX(FlagsPath_,
            TConfigParseError() << "empty flags_path");

        Y_ENSURE_EX(FlagsHost_,
            TConfigParseError() << "empty flags_host");

        CountersHolders_.Reset(MakeHolder<TSharedCountersHolders>(mp.Control->SharedStatsManager(), ServiceName_));
    }

private:
    START_PARSE {
        if (key == "flags") {
            TSubLoader(Copy(value->AsSubConfig())).Swap(FlagsModule_);
            return;
        }

        ON_KEY("file_switch", KillSwitchFile_) {
            return;
        }

        ON_KEY("service_name", ServiceName_) {
            return;
        }

        ON_KEY("flags_path", FlagsPath_) {
            return;
        }

        ON_KEY("flags_host", FlagsHost_) {
            return;
        }

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

    THolder<TTls> DoInitTls(IWorkerCtl* worker) override {
        auto tls = MakeHolder<TTls>(*CountersHolders_, worker);
        if (KillSwitchFile_) {
            tls->KillSwitch = worker->SharedFiles()->FileChecker(KillSwitchFile_, TDuration::Seconds(1));
        }
        return tls;
    }

    void DoCheckConstraints() const override {
        CheckHasParent("http");
    }

    TError DoRun(const TConnDescr& descr, TTls& tls) const noexcept override {
        descr.Request->Headers().Delete(ServiceHeader);

        if (tls.DoesKillSwitchExist()) {
            descr.Request->Headers().Delete(ResultHeader);

            descr.ExtraAccessLog << " disabled";
            tls.Disabled.Inc();
            return Submodule_->Run(descr);
        }

        TVector<TStringStorage> result;

        Y_TRY(TError, error) {
            TRequest request = BuildRequest().Method(EMethod::GET)
                .Path(JoinFlagsPath(FlagsPath_, TString(descr.Request->RequestLine().Path.AsStringBuf())))
                .Version11()
                .Header("Host", FlagsHost_);

            if (ServiceName_) {
                request.Headers().Add(ServiceHeader, TStringBuf{ServiceName_});
            }

            TRequester requester(*FlagsModule_, descr);
            TResponse response;
            Y_PROPAGATE_ERROR(requester.Request(std::move(request), response));

            result = response.Headers().GetValuesMove(ResultHeader);

            if (result) {
                descr.ExtraAccessLog << " flags success";
                tls.Success.Inc();
            } else {
                descr.ExtraAccessLog << " flags no header";
                tls.NoHeader.Inc();
            }

            return {};
        } Y_CATCH {
            tls.NoAnswer.Inc();
            LOG_ERROR(TLOG_ERR, descr, "flags failed: " << GetErrorMessage(error));
            descr.ExtraAccessLog << " flags no answer";
        }

        descr.Request->Headers().Replace(ResultHeader, std::move(result));

        return Submodule_->Run(descr);
    }

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

private:
    THolder<IModule> FlagsModule_;

    TString KillSwitchFile_;
    TString ServiceName_;
    TString FlagsPath_ = "/conflagexp";
    TString FlagsHost_ = "yandex.ru";

    THolder<TSharedCountersHolders> CountersHolders_;
};

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