#pragma once

#include <balancer/kernel/custom_io/null.h>
#include <balancer/kernel/custom_io/queue.h>
#include <balancer/kernel/custom_io/stream.h>
#include <balancer/kernel/fs/shared_file_exists_checker.h>
#include <balancer/kernel/fs/shared_files.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/io/iobase.h>
#include <balancer/kernel/memory/chunks.h>
#include <balancer/kernel/module/iface.h>
#include <balancer/kernel/module/module.h>

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

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

namespace NExpCommon {
    using namespace NSrvKernel;
    using namespace NJson;

    const NRegExp::TFsm& ExpHeaders() noexcept;

    static const TStringBuf STAFF_HEADER_NAME = "X-Yandex-Is-Staff-Login";

    class TRemoteLogOutput final : public IHttpOutput {
    public:
        TRemoteLogOutput(IHttpOutput* slave) noexcept
            : Slave_(slave)
        {}

        ui32 GetStatusCode() const noexcept {
            return StatusCode_;
        }

    private:
        TError DoSendHead(TResponse&& response, const bool forceClose, TInstant deadline) override;

        TError DoSend(TChunkList lst, TInstant deadline) noexcept override;

        TError DoSendTrailers(THeaders&&, TInstant) override {
            Y_FAIL("trailers are not supported");
        }

    private:
        IHttpOutput* const Slave_ = nullptr;
        ui32 StatusCode_ = 0;
    };

    class IHeaderProcessor {
    public:
        virtual TMaybe<TString> Process(TStringBuf header, TStringBuf value) noexcept = 0;
    };

    class TJsonNoExceptWriter {
    public:
        TJsonNoExceptWriter(IExceptionlessOutputStream* out) noexcept;
        void Open() noexcept;
        void Close() noexcept;
        void WriteRequestLine(const TRequestLine& requestLine) noexcept;
        void WriteHeaders(const THeaders& headers) noexcept;
        void WriteHeadersLength(const size_t headersTotalLength) noexcept;
        void WriteResponseTime(const TDuration responseTime) noexcept;
        void WriteProcessedHeaders(const THeaders& headers, IHeaderProcessor* headerProcessor) noexcept;
        void WriteTimestamp(const time_t timestamp) noexcept;
        void WriteUaasModeFlag() noexcept;
        template <class T>
        void WriteKeyValue(TStringBuf key, const T& value) noexcept {
            if (!HasExceptWriter_) {
                try {
                    Writer_->Write(key, value);
                } catch (...) {
                    HasExceptWriter_ = true;
                }
            }
        }
        void WriteBackendErrors(const size_t backendErrors) noexcept;
        void WriteStatus(ui32 status) noexcept;
        void WriteAddr(TStringBuf addr) noexcept;
        void WriteService(TStringBuf service) noexcept;

        bool HasExcept() const noexcept {
            return HasExceptWriter_;
        }

    private:
        THolder<TJsonWriter> Writer_;
        bool HasExceptWriter_ = false;
    };

    class TRemoteLoggerProperties {
    public:
        explicit TRemoteLoggerProperties(IModule* remoteLogStorage) : RemoteLogStorage_(remoteLogStorage) {}

        size_t CompressLevel() const noexcept;
        bool NoRemoteLogFileExists() const noexcept;

        IModule* RemoteLogStorage() noexcept {
            return RemoteLogStorage_;
        }

        void Init(IWorkerCtl* process, const TString& noRemoteLogFile, const TString& compressLevelFile) noexcept;

    private:
        IModule* RemoteLogStorage_;

        //stop remote logging file
        TSharedFileExistsChecker NoRemoteLogFileChecker_;

        //remote log compression level file
        TSharedFileReReader CompressLevelFileReader_;
        mutable TSharedFileReReader::TData CompressLevelFileData_;
    };

    class TRemoteLogger {
    public:
        TRemoteLogger(TRemoteLoggerProperties* logger, IWorkerCtl& process, size_t limit,
            TSharedCounter inQueue, TSharedCounter dropped) noexcept
            : Logger_(logger)
            , Process_(process)
            , Queue_(&process.Executor(), limit)
            , InQueue_(std::move(inQueue))
            , Dropped_(std::move(dropped))
        {
            LoggingTask_ = TCoroutine{"remote_logger", &process.Executor(), [this] {
                do {
                    Y_TRY(TError, err) {
                        return Send();
                    } Y_CATCH {
                        if (ErrorHasErrno(err, {ECANCELED})) {
                            return;
                        }
                    };
                    Process_.Executor().Running()->Yield();
                    InQueue_.Set(Queue_.InQueue());
                    Dropped_.Set(Queue_.Dropped());
                } while (!Process_.Executor().Running()->Cancelled());
            }};
        }

        size_t InQueue() const noexcept {
            return Queue_.InQueue();
        }

        size_t Dropped() const noexcept {
            return Queue_.Dropped();
        }

        void Add(bool hasExcept, TChunkList&& lst) noexcept;

    private:
        TError Send() noexcept;

    private:
        TRemoteLoggerProperties* const Logger_ = nullptr;
        IWorkerCtl& Process_;
        TLimitedChunkQueue Queue_;
        TSharedCounter InQueue_;
        TSharedCounter Dropped_;
        TCoroutine LoggingTask_;
    };
}
