#pragma once

#include "legacy_range.h"

#include <balancer/kernel/helpers/ranges.h>
#include <balancer/kernel/custom_io/stream.h>

#include <library/cpp/monlib/metrics/labels.h>

#include <util/generic/maybe.h>

namespace NReport {
    using namespace NSrvKernel;
    using namespace NLegacyRange;

    enum class ESignalSet {
        Full     /* "full" */,
        Default  /* "default" */,
        Reduced  /* "reduced" */,
        Minimal  /* "minimal" */,
    };

    struct TLocalStats {
        NLegacyRange::TDimData DimData;
        TDuration TotalTime;
        TDuration BackendTime;
        TDuration TimeToFirstByte;
        size_t InputSize = 0;
        size_t OutputSize = 0;
        unsigned StatusCode = 0;
    };

    class TBaseCounter : public TCombinedCounter {
    public:
        TBaseCounter() = default;
        TBaseCounter(const TBaseCounter& other, size_t workerId);
        virtual void Init(const TString& uuid, NMonitoring::TLabels labels, TSharedStatsManager& statsManager) = 0;

        const TString& Name() const noexcept {
            return Name_;
        }

    protected:
        TString Name_;
    };

    class TStatsCounter : public TBaseCounter {
    public:
        using TBaseCounter::TBaseCounter;

        TStatsCounter() = default;
        explicit TStatsCounter(TString name);
        TStatsCounter& SetName(TString name);
        void Init(const TString& uuid, NMonitoring::TLabels labels, TSharedStatsManager& statsManager) override;
    };

    class TStatsGauge : public TBaseCounter {
    public:
        using TBaseCounter::TBaseCounter;

        TStatsGauge() = default;
        explicit TStatsGauge(TString name);
        TStatsGauge& SetName(TString name);
        void Init(const TString& uuid, NMonitoring::TLabels labels, TSharedStatsManager& statsManager) override;
    };

    class TDimHistogram {
    public:
        explicit TDimHistogram(TDimFilter filter);
        TDimHistogram(const TDimHistogram& other, size_t workerId);
        void Init(const TString& uuid, NMonitoring::TLabels labels,
                  TVector<ui64> intervals, TSharedStatsManager& statsManager,
                  TMaybe<bool> disableSslness, TMaybe<bool> disableRobotness, double scale = 1);
        void Add(const TDimData& data, ui64 value);
    private:
        TDimFilter DimFilter_;
        TVector<TMaybe<TSharedHistogram>> Histograms_;
        bool DisableRobotness_ = false;
    };


    using TCodesSet = THashSet<unsigned>;


    class TRecordRanges {
    public:
        TRecordRanges(
            const TMaybe<TDurationRanges>& legacyTimes,
            const TMaybe<TDurationRanges>& backendTimes,
            const TMaybe<TDurationRanges>& clientFailTimeRanges,
            const TMaybe<TDurationRanges>& firstByteTimeRanges,
            const TMaybe<TSizeRanges>& inputSizeRanges,
            const TMaybe<TSizeRanges>& outputSizeRanges,
            const TMaybe<TSizeRanges>& inputHeadersSizeRanges,
            const TMaybe<TSizeRanges>& failedInputHeadersSizeRanges,
            NLegacyRange::TDimFilter filter,
            TMaybe<bool> disableSslness,
            TMaybe<bool> disableRobotness
        ) noexcept;

        TRecordRanges(const TRecordRanges& other, size_t workerId);

        void RegisterSharedSignals(const TString& uuid, const NMonitoring::TLabels& labels,
            TSharedStatsManager& statsManager, THashSet<TString>& disabledSignals);

        void RegisterLegacyTimes(const TDuration& duration, const NLegacyRange::TDimData& data) noexcept;

        void RegisterBackendTime(TDuration duration) noexcept;

        void RegisterInputSize(size_t inputSize) noexcept;

        void RegisterOutputSize(size_t outputSize) noexcept;

        void RegisterClientFail(TDuration duration) noexcept;

        void RegisterFirstByte(TDuration duration) noexcept;

        void RegisterInputHeadersSize(size_t headersSize) noexcept;

        void RegisterFailedInputHeadersSize(size_t headersSize) noexcept;

        bool NeedLegacyTimes() const noexcept {
            return LegacyTimes_.Defined();
        }

        bool UpdateRanges(const TRecordRanges& other) noexcept;

    private:
        template <class TRanges>
        void RegisterGenericHistogram(TSharedStatsManager& statsManager, TMaybe<TSharedHistogram>& res, const TMaybe<TRanges>& ranges, TString uuid, TString name, const NMonitoring::TLabels& labels, THashSet<TString>& disabledSignals) {
            if (disabledSignals.contains(name)) {
                disabledSignals.erase(name);
                return;
            }

            if (!ranges.Defined()) {
                return;
            }

            auto intervals = RangesToIntervals(ranges->Ranges());
            if (intervals.size() == 0) {
                return;
            }

            auto hist = statsManager.MakeHistogram(TStringBuilder{} << "report-" << uuid << "-" << name, std::move(intervals)).AllowDuplicate().Labels(labels);
            if constexpr (std::is_same_v<TRanges, TDurationRangedData<size_t>>) {
                hist.Scale(1e6);
            } else {
                static_assert(std::is_same_v<TRanges, TSizeRangedData<size_t>>, "invalid type for RegisterGenericHistogram");
            }
            res = hist.Build();
        }

        TMaybe<TSharedHistogram> CreateHistogramOrNothing(const TMaybe<TSharedHistogram>& histogram, size_t workerId);

        TMaybe<TDurationRangedData<NLegacyRange::TStorage>> LegacyTimes_;
        TMaybe<TDurationRangedData<size_t>> BackendTimes_;
        TMaybe<TDurationRangedData<size_t>> ClientFailTimes_;
        TMaybe<TDurationRangedData<size_t>> FirstByte_;
        TMaybe<TSizeRangedData<size_t>> InputSizes_;
        TMaybe<TSizeRangedData<size_t>> OutputSizes_;
        TMaybe<TSizeRangedData<size_t>> InputHeadersSizes_;
        TMaybe<TSizeRangedData<size_t>> FailedInputHeadersSizes_;

        TMaybe<TSharedHistogram> BackendTimesShared_;
        TMaybe<TSharedHistogram> ClientFailShared_;
        TMaybe<TSharedHistogram> FirstByteShared_;
        TMaybe<TSharedHistogram> InputSizesShared_;
        TMaybe<TSharedHistogram> OutputSizesShared_;
        TMaybe<TSharedHistogram> ProcessingTimesShared_;
        TMaybe<TSharedHistogram> InputHeadersSizesShared_;
        TMaybe<TSharedHistogram> FailedInputHeadersSizesShared_;

        TMaybe<TSharedHistogram> InternalrobotShared_;
        TMaybe<TSharedHistogram> ExternalunknownShared_;

        const TDimFilter CodeFilter_ = TDimFilter{}.Set(EDim::Status);
        const TDimFilter CodeIRFilter_ = TDimFilter{}.Set(EDim::Status).Set(EDim::Internal).Set(EDim::Robot);
        const TDimFilter CodeIRSlFilter_ = TDimFilter{}.Set(EDim::Status).Set(EDim::Internal)
                                                            .Set(EDim::Robot).Set(EDim::Ssl);

        TVector<TDimHistogram> DimHistograms_;
        NLegacyRange::TDimFilter DimFilter_;
        TMaybe<bool> DisableSslness_;
        TMaybe<bool> DisableRobotness_;
    };

    enum class EFail {
        Conn /*"conn_fail"*/,
        Backend /*"backend_fail"*/,
        Client /*"client_fail"*/,
        Other /*"other_fail"*/,
    };

    class TRecordData : public TSimpleRefCount<TRecordData>, public TNonCopyable {
    public:
        TRecordData(
            TRecordRanges rangesData,
            const TCodesSet& codesSet,
            TString uuid,
            NMonitoring::TLabels labels,
            TSharedStatsManager& statsManager,
            THashSet<TString> disabledSignals
        );

        TRecordData(const TRecordData& other, size_t workerId);

        const TString& Uuid() const noexcept {
            return Uuid_;
        }

        const TRecordRanges& Ranges() const noexcept {
            return Ranges_;
        }

        void RegisterResponseEnd(const TLocalStats& stats) noexcept;

        void UpdateInputSpeed(ui64 delta) noexcept {
            InputSpeed_ += delta;
        }

        void UpdateOutputSpeed(ui64 delta) noexcept {
            OutputSpeed_ += delta;
        }

        void RegisterInProg() noexcept {
            ++InProg_;
        }

        void RegisterSucc(const TMaybe<size_t>& headersSize) noexcept;

        void RegisterFail(const TDuration duration, EFail fail, const TMaybe<size_t>& headersSize) noexcept;

        void RegisterRequest() noexcept {
            ++Requests_;
        }

        void RegisterKeepAlive() noexcept {
            ++KeepAlive_;
        }

        void RegisterNonKeepAlive() noexcept {
            ++NonKeepAlive_;
        }

        void RegisterReused() noexcept {
            ++Reused_;
        }

        void RegisterNotReused() noexcept {
            ++NotReused_;
        }

        void RegisterConnStats(const TConnStats& delta) noexcept;

        void RegisterTimeToFirstByte(TDuration duration) noexcept {
            Ranges_.RegisterFirstByte(duration);
        }

        size_t InProg() const noexcept {
            return InProg_.Value();
        }

        size_t Fail() const noexcept {
            return Fail_.Value();
        }

        size_t Succ() const noexcept {
            return Succ_.Value();
        }

        size_t KeepAlive() const noexcept {
            return KeepAlive_.Value();
        }

        size_t NonKeepAlive() const noexcept {
            return NonKeepAlive_.Value();
        }

        size_t Reused() const noexcept {
            return Reused_.Value();
        }

        size_t NotReused() const noexcept {
            return NotReused_.Value();
        }

        size_t ConnRefused() const noexcept {
            return ConnRefused_.Value();
        }

        size_t ConnTimeout() const noexcept {
            return ConnTimeout_.Value();
        }

        size_t ConnOtherError() const noexcept {
            return ConnOtherError_.Value();
        }

        size_t NoBackendsError() const noexcept {
            return NoBackendsError_.Value();
        }

        bool NeedLegacyTimes() const noexcept {
            return Ranges_.NeedLegacyTimes();
        }

        bool UpdateRanges(const TRecordRanges& other) {
            return Ranges_.UpdateRanges(other);
        }

        void DisableSslness(bool value);

        void DisableRobotness(bool value);

    private:
        static constexpr size_t CategoriesCount = 5;

        TRecordRanges Ranges_;
        TString Uuid_;
        NMonitoring::TLabels Labels_;

        TMaybe<bool> DisableSslness_ = Nothing();
        TMaybe<bool> DisableRobotness_ = Nothing();

        TStatsGauge InProg_{"inprog"};
        TStatsCounter Succ_{"succ"};
        TStatsCounter Fail_{"fail"};
        TStatsCounter Requests_{"requests"};
        TStatsCounter ConnFail_{"conn_fail"};
        TStatsCounter BackendFail_{"backend_fail"};
        TStatsCounter ClientFail_{"client_fail"};
        TStatsCounter OtherFail_{"other_fail"};
        TStatsCounter KeepAlive_{"ka"};
        TStatsCounter NonKeepAlive_{"nka"};
        TStatsCounter Reused_{"reused"};
        TStatsCounter NotReused_{"nreused"};
        TStatsCounter BackendKeepaliveReused_{"backend_keepalive_reused"};
        TStatsCounter ConnRefused_{"conn_refused"};
        TStatsCounter ConnTimeout_{"conn_timeout"};
        TStatsCounter ConnOtherError_{"conn_other_error"};
        TStatsCounter NoBackendsError_{"no_backends_error"};
        TStatsCounter ClientTimeout_{"client_timeout"};
        TStatsCounter BackendTimeout_{"backend_timeout"};
        TStatsCounter BackendAttempt_{"backend_attempt"};
        TStatsCounter LimitedBackendAttempt_{"limited_backend_attempt"};
        TStatsCounter BackendError_{"backend_error"};
        TStatsCounter ClientError_{"client_error"};
        TStatsCounter Code503_{"sc_503"};
        TStatsCounter InputSpeed_{"input_speed"};
        TStatsCounter OutputSpeed_{"output_speed"};
        TStatsCounter BackendHedgedAttempts_{"hedged_attempts"};
        TStatsCounter BackendHedgedSucc_{"hedged_succ"};
        TStatsCounter BackendWriteError_{"backend_write_error"};
        TStatsCounter BackendShortReadAnswer_{"backend_short_read_answer"};

        std::array<TStatsCounter, CategoriesCount> StatusCodeCategs_;
        std::array<TStatsCounter, CategoriesCount> OutgoingStatusCodeCategs_;
        std::array<TStatsCounter, CategoriesCount> InternalRobotStatusCodeCategs_;
        std::array<TStatsCounter, CategoriesCount> ExternalUnknownStatusCodeCategs_;

        THashMap<unsigned, TStatsCounter> OutgoingStatusCodes_;
        THashMap<unsigned, TStatsCounter> ExternalUnknownStatusCodes_;

        // This is why TRecordData cannot be moved.
        std::vector<TBaseCounter*> AllCounters_ {
            &InProg_,
            &Succ_,
            &Fail_,
            &Requests_,
            &ConnFail_,
            &BackendFail_,
            &ClientFail_,
            &OtherFail_,
            &KeepAlive_,
            &NonKeepAlive_,
            &Reused_,
            &NotReused_,
            &BackendKeepaliveReused_,
            &ConnRefused_,
            &ConnTimeout_,
            &ConnOtherError_,
            &NoBackendsError_,
            &ClientTimeout_,
            &BackendTimeout_,
            &BackendAttempt_,
            &LimitedBackendAttempt_,
            &BackendError_,
            &ClientError_,
            &Code503_,
            &InputSpeed_,
            &OutputSpeed_,
            &BackendHedgedAttempts_,
            &BackendHedgedSucc_,
            &BackendWriteError_,
            &BackendShortReadAnswer_,
        };

        std::vector<TBaseCounter*> UnistatOnlyCounters_;
    };

    using TRecordDataPtr = TIntrusivePtr<TRecordData>;
    using TRecordDataVec = std::deque<TRecordDataPtr>;
}
