#pragma once

#include "per_worker_backend.h"
#include <util/digest/murmur.h>
#include <util/random/fast.h>

#include <balancer/kernel/balancer/algorithm.h>

// TODO(velavokr): make it a kernel library
#include <balancer/kernel/http2/server/utils/http2_flat_heap.h>


namespace NSrvKernel::NDynamicBalancing {
    using ::TDuration;

    // Chunk of statistics for TDynamicBackend
    struct TDynamicBackendStats {
        ui64 Requests = 0;
        ui64 Fails = 0;
        ui64 Timeouts = 0;
        ui64 ConnErrors = 0;
        ui64 SumProcessingTime = 0;
        TInstant StartedFrom;

        TDynamicBackendStats()
            : StartedFrom(TInstant::Now())
        {
        }
    };

    // Thread-safe storage of statistics for TDynamicBackend
    class TDynamicBackendStatsStorage {
    public:
        TDynamicBackendStatsStorage(ui64 minRequests, TDuration historyInterval);
        TDynamicBackendStats CurrentStats() const noexcept;
        void AddRequest(const TError& error, const TDuration& duration) noexcept;
        void DropOldStats() noexcept;
    private:
        void IncIndex(TInstant start) noexcept;
        size_t Tail(size_t offset = 0) const noexcept;
        TMutex Mutex_;
        size_t Index_ = 0;
        static constexpr size_t CHUNKS = 10;
        TDynamicBackendStats Total_;
        TDynamicBackendStats Chunks_[CHUNKS];
        const ui64 MinRequests_;
        const TDuration ChunkMinDuration_;
    };

    // IBackend implementation for dynamic algorithms (thread-safe)
    class TDynamicBackend : public NSrvKernel::TPerWorkerBaseBackend, public TMoveOnly, public TThrRefBase {
    public:
        enum class TStatus {
            Unhealthy   /* "UNHEALTHY" */,
            Critical    /* "CRITICAL" */,
            Degraded    /* "DEGRADED" */,
            Healthy     /* "HEALTHY" */,
            Blacklisted /* "BLACKLISTED" */,
            NotReady    /* "NOT_READY" */,
            Recovering  /* "RECOVERING" */,
            NotChecked  /* "NOT_CHECKED" */,
            Max,
        };

        TDynamicBackend(TBackendDescriptor::TRef descr, ui64 minRequests, TDuration historyInterval) noexcept
            : TPerWorkerBaseBackend(std::move(descr))
            , Hash(MurmurHash<ui64>(Name().data(), Name().size()))
            , Stats_(minRequests, historyInterval)
        {
            ResetConsecutiveFails();
        }

        void OnFailRequest(const TError& error, const TDuration& duration) noexcept override;
        void OnCompleteRequest(const TDuration& duration) noexcept override;

        double FinalWeight() const noexcept {
            return OriginalWeight() * WeightCoeff;
        }

        void ResetConsecutiveFails() noexcept;

        void Dump(NJson::TJsonWriter& out, bool extended) const noexcept;

        void SetWeightFromPing(double value, bool valueFromBackend) noexcept override;
        void SetWeightNormalizationCoeff(double value) noexcept;

        TDynamicBackendStats CurrentStats() const noexcept {
            return Stats_.CurrentStats();
        }

        void DropOldStats() noexcept {
            Stats_.DropOldStats();
        }

        TString GroupName() const noexcept {
            return Backend_->GroupName();
        }

        void AddSelection() noexcept {
            Selections.fetch_add(1);
        }
        void AddExpectation() noexcept {
            Expectations.fetch_add(1);
        }
    public:
        const ui64 Hash = 0;

        std::atomic<TStatus> Status = TStatus::Healthy;
        std::atomic<bool> Pessimized = false;
        std::atomic<double> WeightCoeff = 1;
        std::atomic<double> TargetWeightCoeff = 1;
        std::atomic<int> PessimizationStepsRemaining = 0;
        std::atomic<int> CurrentPessimizationInterval = 1;

        std::atomic<ui64> TrackedConsecutiveRequests;
        std::atomic<ui64> ConsecutiveFails;
        std::atomic<ui64> ConsecutiveFailsMax;

        std::atomic<bool> ActiveChecked = false;

        std::atomic<ui64> Selections = 0;
        std::atomic<ui64> Expectations = 0;
    private:
        TDynamicBackendStatsStorage Stats_;
        std::atomic<double> WeightNormalizationCoeff_ = 1;
    };

    class TDynamicBackendHolder final : public TIntrusiveListItem<TDynamicBackendHolder>, public TMoveOnly {
    public:
        TDynamicBackendHolder() noexcept = default;
        TDynamicBackendHolder(TIntrusivePtr<TDynamicBackend> backend) noexcept
            : Backend_(std::move(backend))
        {}

        TDynamicBackend* operator->() const noexcept {
            return Backend_.Get();
        }

        TDynamicBackend* Get() const noexcept {
            return Backend_.Get();
        }

        TIntrusivePtr<TDynamicBackend> Clone() const noexcept {
            return Backend_;
        }

    private:
        TIntrusivePtr<TDynamicBackend> Backend_;
    };

    // Immutable snapshot of some TDynamicBackend values
    struct TBackendStateSnapshot {
        double Weight;
        TString GroupName;
    };

    // TBalancingState is a precomputed data structure constructed from current state of pessimization algorithm.
    // Mutable fields of TDynamicBackend may change at any moment.
    // Other fields should be considered as immutable snapshots,
    // and may be used this way to take any balancing decisions.
    class TBalancingState : public TThrRefBase {
    public:
        explicit TBalancingState(TVector<TIntrusivePtr<TDynamicBackend>> balancingState);
        void Dump(NJson::TJsonWriter& out) const noexcept;

        const TVector<TIntrusivePtr<TDynamicBackend>>& Backends() const noexcept {
            return Backends_;
        }
        const THashMap<IBackend*, TBackendStateSnapshot>& StateSnapshots() const noexcept {
            return Snapshots_;
        }
        const THashMap<TString, TBackendsGroupWeights>& GroupsWeights() const noexcept {
            return Groups_;
        }
        const TBackendsGroupWeights& AllWeights() const noexcept {
            return AllWeights_;
        }
    private:
        TVector<TIntrusivePtr<TDynamicBackend>> Backends_;
        THashMap<IBackend*, TBackendStateSnapshot> Snapshots_;
        THashMap<TString, TBackendsGroupWeights> Groups_;
        TBackendsGroupWeights AllWeights_;
    };
}
