#pragma once

#include <balancer/kernel/memory/alloc.h>
#include <balancer/kernel/helpers/disposable.h>
#include <balancer/kernel/stats/manager.h>

#include <library/cpp/coroutine/engine/impl.h>

#include <library/cpp/logger/log.h>
#include <library/cpp/logger/backend.h>
#include <library/cpp/logger/record.h>

#include <util/draft/holder_vector.h>

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

#include <util/system/condvar.h>
#include <util/system/mutex.h>

#include <util/thread/factory.h>
#include "log.h"

namespace NSrvKernel {
    using TBackendPtr = TAtomicSharedPtr<TLogBackend>;

    struct TLogQueueOpts {
        size_t MaxQueueSize = 4096;
        size_t SubmitAttemptsCount = 32;
        TDuration FlushInterval = TDuration::MilliSeconds(100);
    };

    class TMetaLogBackend: public TLogBackend {
    public:
        TMetaLogBackend(TLog* log) noexcept
            : Log_(log)
        {}

        void WriteData(const TLogRecord& rec) override {
            Log_->Write(rec.Priority, rec.Data, rec.Len);
        }

        void ReopenLog() override {
            Log_->ReopenLog();
        }

    private:
        TLog* const Log_ = nullptr;
    };

    class TLogRecordQueue
        : public TAtomicRefCount<TLogRecordQueue>
    {
        class TSelfSufficientLogRecord
            : public TNonCopyable
            , private TString
            , public TLogRecord
        {
        public:
            TSelfSufficientLogRecord(const TLogRecord& record)
                : TString(record.Data, record.Len)
                , TLogRecord(record.Priority, this->data(), this->size())
            {}
        };

    public:
        TLogRecordQueue(const TLogQueueOpts& opts)
            : Opts_(opts)
            , RecommendedQueueSize_((Opts_.MaxQueueSize + Opts_.SubmitAttemptsCount - 1) / Opts_.SubmitAttemptsCount)
        {
            Queue_.reserve(Opts_.MaxQueueSize);
        }

        bool TryEnqueue(const TLogRecord&);
        bool SubmitRecommended() const;

        using TQueueType = THolderVector<TSelfSufficientLogRecord>;
        const TQueueType& GetData() const noexcept;

    private:
        TLogQueueOpts Opts_;
        size_t RecommendedQueueSize_;
        TQueueType Queue_;
    };

    using TLogRecordQueuePtr = TIntrusivePtr<TLogRecordQueue>;

    class TLogsThread
        : public IThreadFactory::IThreadAble
        , public IDisposable
    {
    public:
        TLogsThread(TContExecutor& executor, IThreadFactory& pool, TBackendPtr slave);
        ~TLogsThread() noexcept override;

        bool TrySubmit(TLogRecordQueuePtr queue) noexcept;  // To be called from another thread
        void ReopenLog();                        // To be called from another thread

    private:
        void DoExecute() override ;
        void DoDispose() noexcept override ;
        void HandleQueue(const TLogRecordQueue& queue);
        template <typename F> void DoWithTryGuard(F&& action);
        void DoReopenLog() noexcept;

    private:
        enum EReopenState {
            RS_READY,
            RS_REQUESTED,
            RS_PROCESSED
        };

        TBackendPtr Slave_;
        TMutex Mutex_;
        TCondVar CondVar_;
        THolder<IThreadFactory::IThread> Thread_;
        TLogRecordQueuePtr ReceivedQueue_;
        TContExecutor& Executor_;
        TAtomic ReopenState_ = EReopenState::RS_READY;
        std::exception_ptr ReopenError_ = nullptr;
        TAtomic IsShutdowned_ = 0;
    };

    class TContextSafeLogBackend
        : public TLogBackend
        , public IDisposable
    {
    public:
        TContextSafeLogBackend(TContExecutor* executor, const TLogQueueOpts& opts, TLogsThread* thread,
                TSharedCounter processedItems, TSharedCounter lostItems);
        ~TContextSafeLogBackend() noexcept override;

        void WriteData(const TLogRecord& rec) override;
        void ReopenLog() override;

    private:
        void DoDispose() noexcept override ;
        void TrySubmit(bool destroyOnFailure) noexcept;
        void SubmitLoop(TCont*) noexcept;

    private:
        TContExecutor* Executor_ = nullptr;
        TLogQueueOpts Opts_;
        TLogRecordQueuePtr Queue_;
        TLogsThread* LogsThread_ = nullptr;
        TCont* SubmitCont_ = nullptr;
        TAtomicState SubmitContState_;
        TSharedCounter ProcessedLogItems_;
        TSharedCounter LostLogItems_;
    };

    template <class T>
    class TContextSafeLog : public T {
    public:
        TContextSafeLog() = default;
        TContextSafeLog(THolder<TContextSafeLogBackend> backend) {
            ResetBackend(std::move(backend));
        }
        void ResetBackend(THolder<TContextSafeLogBackend> backend) noexcept {
            LogBackend_ = backend.Get();
            T::ResetBackend(std::move(backend));
        }
        void DisposeBackend() noexcept {
            Y_ASSERT(LogBackend_);
            LogBackend_->Dispose();
        }

    private:
        TContextSafeLogBackend* LogBackend_ = nullptr;
    };

    template <class T>
    class TContextSafeLogGenerator : public T, public IDisposable {
    public:
        TContextSafeLogGenerator() = default;

        TContextSafeLogGenerator(THolder<TLogBackend> backend) : T(std::move(backend)) {};

        TContextSafeLog<T> GenerateThreadedLog(TContExecutor* executor, size_t workerId) const {
            return TContextSafeLog<T>(MakeHolder<TContextSafeLogBackend>(executor, Opts_, LogsThread_.Get(),
                TSharedCounter(*ProcessedLogItems_, workerId), TSharedCounter(*LostLogItems_, workerId)));
        }

        void CreateWritingThreadAndCommonStuff(TContExecutor& executor, const TLogQueueOpts& opts, IThreadFactory& pool,
            TSharedCounter& processedItems, TSharedCounter& lostItems, size_t workerId) {
            LogsThread_ = MakeHolder<TLogsThread>(executor, pool, T::ReleaseBackend());
            Opts_ = opts;
            ProcessedLogItems_ = &processedItems;
            LostLogItems_ = &lostItems;
            auto logBackend = MakeHolder<TContextSafeLogBackend>(&executor, Opts_, LogsThread_.Get(),
                TSharedCounter(*ProcessedLogItems_, workerId), TSharedCounter(*LostLogItems_, workerId));
            LogBackend_ = logBackend.Get();
            T::ResetBackend(std::move(logBackend));
        }

    private:
        void DoDispose() noexcept override {
            if (LogBackend_) {
                LogBackend_->Dispose();
            }
            // in fork model in master process we don't have LogsThread_
            if (LogsThread_) {
                LogsThread_->Dispose();
            }
        }

    private:
        THolder<TLogsThread> LogsThread_;
        TLogQueueOpts Opts_;
        TContextSafeLogBackend* LogBackend_ = nullptr;
        TSharedCounter* ProcessedLogItems_ = nullptr;
        TSharedCounter* LostLogItems_ = nullptr;
    };
}
