#include "module.h"

#include <balancer/kernel/custom_io/chunkio.h>
#include <balancer/kernel/custom_io/null.h>
#include <balancer/kernel/custom_io/stream.h>
#include <balancer/kernel/fs/shared_file_exists_checker.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/module/module.h>
#include <balancer/kernel/regexp/regexp_pire.h>

#include <util/folder/path.h>
#include <util/generic/strbuf.h>
#include <util/stream/file.h>
#include <util/string/ascii.h>
#include <util/string/strip.h>

using namespace NSrvKernel;

namespace {
constexpr const char THDB_VERSION_HEADER[] = "x-thdb-version";
constexpr const ui64 MAX_VERSION_LENGTH = 256;

class THeaderValueFsm : public TFsm, public TWithDefaultInstance<THeaderValueFsm> {
public:
    THeaderValueFsm() noexcept
        : TFsm("[-0-9a-zA-Z_]+", TFsm::TOptions().SetCaseInsensitive(false).SetSurround(false)) {}
};
}

Y_TLS(thdb_version) {
    bool IsValidVersion(const TString& version) const noexcept {
        if (version.size() > MAX_VERSION_LENGTH) {
            return false;
        }

        TMatcher matcher{ THeaderValueFsm::Instance() };
        return matcher.Match(version).Final();
    }

    const TString& GetThdbVersion() noexcept {
        const auto& data = FileReader.Data();
        if (data.Id() != FileReaderData.Id()) {
            FileReaderData = data;
            auto version = Strip(FileReaderData.Data());
            if (IsValidVersion(version)) {
                LastVersion = version;
            }
        }

        return  LastVersion;
    }

    TSharedFileReReader FileReader;
    TSharedFileReReader::TData FileReaderData;
    TString LastVersion;
};

MODULE_WITH_TLS_BASE(thdb_version, TModuleWithSubModule) {

    class TThdbVersionHeaderFsm : public TFsm, public TWithDefaultInstance<TThdbVersionHeaderFsm> {
    public:
        TThdbVersionHeaderFsm() noexcept
            : TFsm(THDB_VERSION_HEADER, TFsm::TOptions().SetCaseInsensitive(true).SetSurround(false))
        {}
    };

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

        if (FileReadTimeout_ == TDuration()) {
            ythrow TConfigParseError() << "Set file read timeout";
        }

        if (FileName_.empty()) {
            ythrow TConfigParseError() << "Set file name";
        }

        if (!Submodule_) {
            ythrow TConfigParseError() << "no submodule configured";
        }

        Y_UNUSED(THeaderValueFsm::Instance());
        Y_UNUSED(TThdbVersionHeaderFsm::Instance());
    }

private:
    START_PARSE {
        ON_KEY("file_read_timeout", FileReadTimeout_) {
            return;
        }

        ON_KEY("file_name", FileName_) {
            return;
        }

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

private:
    THolder<TTls> DoInitTls(IWorkerCtl* worker) override {
        auto tls = MakeHolder<TTls>();
        if (FileName_) {
            tls->FileReader = worker->SharedFiles()->FileReReader(FileName_, FileReadTimeout_);
        }
        return tls;
    }

    void RemoveThdbVersionHeader(const TConnDescr& descr) const noexcept {
        descr.Request->Headers().Delete(TThdbVersionHeaderFsm::Instance());
    }

    void AddHeader(const TConnDescr& descr, TTls& tls) const noexcept {
        const auto& version = tls.GetThdbVersion();

        if (version && descr.Request) {
            descr.Request->Headers().Add(THDB_VERSION_HEADER, version);
        }
    }

    TError DoRun(const TConnDescr& descr, TTls& tls) const noexcept override {
        RemoveThdbVersionHeader(descr);
        AddHeader(descr, tls);
        return Submodule_->Run(descr);
    }

private:
    TDuration FileReadTimeout_;
    TString FileName_;
};

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