#include <library/cpp/testing/unittest/registar.h>

#include <balancer/modules/report/record_data.h>

#include <balancer/kernel/net/address.h>
#include <balancer/kernel/module/iface.h>
#include <balancer/kernel/testing/conn_descr.h>
#include <balancer/kernel/testing/process_mock.h>

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

#include <util/datetime/base.h>
#include <util/generic/vector.h>
#include <util/stream/str.h>


using namespace NSrvKernel;
using namespace NReport;
using namespace NSrvKernel::NTesting;

namespace {

    void DoRecordTest() {
        using namespace NLegacyRange;
        TSharedAllocator allocator;
        TSharedStatsManager statsManager(allocator);
        statsManager.SetWorkersCount(0, 0);
        TRecordData record(
            TRecordRanges(TDurationRanges("10ms"), {}, {}, {}, {}, {}, {}, {}, TDimFilter().SetAll(), Nothing(), Nothing()),
            {}, "uuid", NMonitoring::TLabels{}, statsManager, {}
        );
        allocator.Freeze();

        UNIT_ASSERT_VALUES_EQUAL(record.Uuid(), "uuid");
        UNIT_ASSERT_EQUAL(record.InProg(), 0);
        UNIT_ASSERT_EQUAL(record.Succ(), 0);
        record.RegisterInProg();
        UNIT_ASSERT_EQUAL(record.InProg(), 1);
        record.RegisterSucc({});
        UNIT_ASSERT_EQUAL(record.InProg(), 0);
        UNIT_ASSERT_EQUAL(record.Succ(), 1);

        record.RegisterInProg();
        record.RegisterFail(TDuration::Zero(), {}, {});
        UNIT_ASSERT_EQUAL(record.Succ(), 1);
        UNIT_ASSERT_EQUAL(record.Fail(), 1);

        UNIT_ASSERT_EQUAL(record.KeepAlive(), 0);
        record.RegisterKeepAlive();
        UNIT_ASSERT_EQUAL(record.Succ(), 1);
        UNIT_ASSERT_EQUAL(record.Fail(), 1);
        UNIT_ASSERT_EQUAL(record.KeepAlive(), 1);
        UNIT_ASSERT_EQUAL(record.NonKeepAlive(), 0);

        record.RegisterNonKeepAlive();
        UNIT_ASSERT_EQUAL(record.Succ(), 1);
        UNIT_ASSERT_EQUAL(record.Fail(), 1);
        UNIT_ASSERT_EQUAL(record.KeepAlive(), 1);
        UNIT_ASSERT_EQUAL(record.NonKeepAlive(), 1);

        UNIT_ASSERT_EQUAL(record.ConnRefused(), 0);
        UNIT_ASSERT_EQUAL(record.ConnTimeout(), 0);
        UNIT_ASSERT_EQUAL(record.ConnOtherError(), 0);
        UNIT_ASSERT_EQUAL(record.NoBackendsError(), 0);
        record.RegisterConnStats(TConnStats{5, 10, 15, 20} - TConnStats{1, 2, 3, 4});
        UNIT_ASSERT_EQUAL(record.ConnRefused(), 4);
        UNIT_ASSERT_EQUAL(record.ConnTimeout(), 8);
        UNIT_ASSERT_EQUAL(record.ConnOtherError(), 12);
        UNIT_ASSERT_EQUAL(record.NoBackendsError(), 16);
    }

    using THistogramValues = TVector<std::pair<double, ui64>>;

    void DoRecordWithInputSizesTest() {
        using namespace NLegacyRange;
        TSharedAllocator allocator;
        TSharedStatsManager statsManager(allocator);
        statsManager.SetWorkersCount(0, 0);
        TRecordData record(
            TRecordRanges({}, {}, {}, {}, TSizeRanges("5"), {}, {}, {}, TDimFilter().SetAll(), Nothing(), Nothing()),
            {}, "uuid", NMonitoring::TLabels{}, statsManager, {}
        );
        allocator.Freeze();

        for (auto sz : {3, 13, 0, 100}) {
            TLocalStats stats;
            stats.InputSize = sz;
            record.RegisterResponseEnd(stats);
        }

        auto stats = statsManager.GetBalancerStats();
        auto it = std::find_if(stats.Histograms.begin(), stats.Histograms.end(), [](const THistogram& h){
            return h.Name == "report-uuid-input_size";
        });
        UNIT_ASSERT(it != stats.Histograms.end());
        THistogramValues values = {{0, 2}, {5, 2}};
        UNIT_ASSERT_VALUES_EQUAL(it->Values, values);
    }

    void DoRecordWithOutputSizesTest() {
        using namespace NLegacyRange;
        TSharedAllocator allocator;
        TSharedStatsManager statsManager(allocator);
        statsManager.SetWorkersCount(0, 0);
        TRecordData record(
            TRecordRanges({}, {}, {}, {}, {}, TSizeRanges("5"), {}, {}, TDimFilter().SetAll(), Nothing(), Nothing()),
            {}, "uuid", NMonitoring::TLabels{}, statsManager, {}
        );
        allocator.Freeze();

        for (auto sz : {3, 13, 0, 100}) {
            TLocalStats stats;
            stats.OutputSize = sz;
            record.RegisterResponseEnd(stats);
        }

        auto stats = statsManager.GetBalancerStats();
        auto it = std::find_if(stats.Histograms.begin(), stats.Histograms.end(), [](const THistogram& h){
            return h.Name == "report-uuid-output_size";
        });
        UNIT_ASSERT(it != stats.Histograms.end());
        THistogramValues values = {{0, 2}, {5, 2}};
        UNIT_ASSERT_VALUES_EQUAL(it->Values, values);
    }

    void DoRecordWithBackendTimesTest() {
        using namespace NLegacyRange;
        TSharedAllocator allocator;
        TSharedStatsManager statsManager(allocator);
        statsManager.SetWorkersCount(0, 0);
        TRecordData record(
            TRecordRanges({}, TDurationRanges("10ms"), {}, {}, {}, {}, {}, {}, TDimFilter().SetAll(), Nothing(), Nothing()),
            {}, "uuid", NMonitoring::TLabels{}, statsManager, {}
        );
        allocator.Freeze();

        for (auto t : {
            TDuration::Zero(), TDuration::MilliSeconds(1), TDuration::MilliSeconds(10), TDuration::MilliSeconds(100)})
        {
            TLocalStats stats;
            stats.BackendTime = t;
            record.RegisterResponseEnd(stats);
        }

        auto stats = statsManager.GetBalancerStats();
        auto it = std::find_if(stats.Histograms.begin(), stats.Histograms.end(), [](const THistogram& h){
            return h.Name == "report-uuid-backend_time";
        });
        UNIT_ASSERT(it != stats.Histograms.end());
        THistogramValues values = {{0, 2}, {0.01, 2}};
        UNIT_ASSERT_VALUES_EQUAL(it->Values, values);
    }

    void DoRecordWithClientFailTimesTest() {
        using namespace NLegacyRange;
        TSharedAllocator allocator;
        TSharedStatsManager statsManager(allocator);
        statsManager.SetWorkersCount(0, 0);
        TRecordData record(
            TRecordRanges({}, {}, TDurationRanges("10ms"), {}, {}, {}, {}, {}, TDimFilter().SetAll(), Nothing(), Nothing()),
            {}, "uuid", NMonitoring::TLabels{}, statsManager, {}
        );
        allocator.Freeze();

        TConnStats cur{};
        cur.ClientError = 1;

        TMaybe<size_t> headersSize = 100;
        record.RegisterFail(TDuration::Zero(), EFail::Client, headersSize);
        record.RegisterFail(TDuration::MilliSeconds(1), EFail::Client, headersSize);
        record.RegisterFail(TDuration::MilliSeconds(10), EFail::Client, headersSize);
        record.RegisterFail(TDuration::MilliSeconds(100), EFail::Client, headersSize);
        record.RegisterFail(TDuration::MilliSeconds(1), EFail::Other, headersSize);
        record.RegisterFail(TDuration::MilliSeconds(1), EFail::Other, headersSize);
        record.RegisterFail(TDuration::MilliSeconds(1), EFail::Other, headersSize);
        record.RegisterFail(TDuration::MilliSeconds(1), EFail::Backend, headersSize);
        record.RegisterFail(TDuration::MilliSeconds(1), EFail::Conn, headersSize);

        auto stats = statsManager.GetBalancerStats();
        auto it = std::find_if(stats.Histograms.begin(), stats.Histograms.end(), [](const THistogram& h){
            return h.Name == "report-uuid-client_fail_time";
        });
        UNIT_ASSERT(it != stats.Histograms.end());
        THistogramValues values = {{0, 2}, {0.01, 2}};
        UNIT_ASSERT_VALUES_EQUAL(it->Values, values);

        auto c = std::find_if(stats.Counters.begin(), stats.Counters.end(), [](const TCounter& cnt){
            return cnt.Name == "report-uuid-client_fail";
        });
        UNIT_ASSERT(c != stats.Counters.end());
        UNIT_ASSERT_VALUES_EQUAL(c->Value, 4);
        c = std::find_if(stats.Counters.begin(), stats.Counters.end(), [](const TCounter& cnt){
            return cnt.Name == "report-uuid-other_fail";
        });
        UNIT_ASSERT(c != stats.Counters.end());
        UNIT_ASSERT_VALUES_EQUAL(c->Value, 3);
        c = std::find_if(stats.Counters.begin(), stats.Counters.end(), [](const TCounter& cnt){
            return cnt.Name == "report-uuid-backend_fail";
        });
        UNIT_ASSERT(c != stats.Counters.end());
        UNIT_ASSERT_VALUES_EQUAL(c->Value, 2);
        c = std::find_if(stats.Counters.begin(), stats.Counters.end(), [](const TCounter& cnt){
            return cnt.Name == "report-uuid-conn_fail";
        });
        UNIT_ASSERT(c != stats.Counters.end());
        UNIT_ASSERT_VALUES_EQUAL(c->Value, 1);
    }

    void DoRecordWithHeadersSizeTest() {
        TSharedAllocator allocator;
        TSharedStatsManager statsManager(allocator);
        statsManager.SetWorkersCount(0, 0);
        TRecordData record(
                TRecordRanges({}, {}, {}, {}, {}, {}, TSizeRanges{"1024,2048"}, TSizeRanges{"1024,2048"}, TDimFilter().SetAll(), Nothing(), Nothing()),
                {}, "uuid", NMonitoring::TLabels{}, statsManager, {}
        );
        allocator.Freeze();

        record.RegisterSucc({});
        record.RegisterSucc(100);
        record.RegisterSucc(2000);
        record.RegisterSucc(20000);
        TDuration dummy = TDuration::Zero();
        record.RegisterFail(dummy, EFail::Client, {});
        record.RegisterFail(dummy, EFail::Client, 100);
        record.RegisterFail(dummy, EFail::Client, 2000);
        record.RegisterFail(dummy, EFail::Client, 20000);
        record.RegisterFail(dummy, EFail::Other, {});
        record.RegisterFail(dummy, EFail::Other, 100);
        record.RegisterFail(dummy, EFail::Other, 2000);
        record.RegisterFail(dummy, EFail::Other, 20000);
        record.RegisterFail(dummy, EFail::Backend, {});
        record.RegisterFail(dummy, EFail::Backend, 100);
        record.RegisterFail(dummy, EFail::Backend, 2000);
        record.RegisterFail(dummy, EFail::Backend, 20000);
        record.RegisterFail(dummy, EFail::Conn, {});
        record.RegisterFail(dummy, EFail::Conn, 100);
        record.RegisterFail(dummy, EFail::Conn, 2000);
        record.RegisterFail(dummy, EFail::Conn, 20000);

        auto stats = statsManager.GetBalancerStats();
        auto it = std::find_if(stats.Histograms.begin(), stats.Histograms.end(), [](const THistogram& h){
            return h.Name == "report-uuid-input_headers_size";
        });
        THistogramValues values = {{0, 5}, {1024, 5}, {2048, 5}};
        UNIT_ASSERT(it != stats.Histograms.end());
        UNIT_ASSERT_VALUES_EQUAL(it->Values, values);

        it = std::find_if(stats.Histograms.begin(), stats.Histograms.end(), [](const THistogram& h){
            return h.Name == "report-uuid-backend_fail_input_headers_size";
        });
        values = {{0, 1}, {1024, 1}, {2048, 1}};
        UNIT_ASSERT(it != stats.Histograms.end());
        UNIT_ASSERT_VALUES_EQUAL(it->Values, values);
    }

    struct ExpectedSignals {
        TVector<std::pair<TString, ui64>> Counters;
        TVector<std::pair<TString, THistogramValues>> Histograms;
    };

    void BaseTestRange(const TSharedStatsManager& statsManager, TRecordData& record, const ExpectedSignals& expected) {
        using namespace NLegacyRange;
        TProcessMock process;
        TTestConnDescr descr(process);
        auto data = GetDimData(descr.ConnDescr(), 200);

        for (auto t : {
            TDuration::Zero(), TDuration::MilliSeconds(1), TDuration::MilliSeconds(10), TDuration::MilliSeconds(100)})
        {
            TLocalStats stats;
            stats.TotalTime = t;
            stats.DimData = data;
            record.RegisterResponseEnd(stats);
        }

        auto stats = statsManager.GetBalancerStats();
        for (const auto& counter : expected.Counters) {
            auto c = std::find_if(stats.Counters.begin(), stats.Counters.end(), [&counter](const TCounter& cnt){
                return cnt.Name == counter.first;
            });
            UNIT_ASSERT_C(c != stats.Counters.end(), counter.first + " not found");
            UNIT_ASSERT_VALUES_EQUAL_C(c->Value, counter.second, counter.first + " value mismatch");
        }

        for (const auto& hist : expected.Histograms) {
            auto h = std::find_if(stats.Histograms.begin(), stats.Histograms.end(), [&hist](const THistogram& histogram){
                return histogram.Name == hist.first;
            });
            UNIT_ASSERT_C(h != stats.Histograms.end(), hist.first + " not found");
            UNIT_ASSERT_VALUES_EQUAL_C(h->Values, hist.second, hist.first + " values mismatch");
        }
    }

    void DoRecordSingleRangeTest() {
        using namespace NLegacyRange;
        TSharedAllocator allocator;
        TSharedStatsManager statsManager(allocator);
        statsManager.SetWorkersCount(0, 0);
        TRecordData record(
            TRecordRanges(TDurationRanges("10ms"), {}, {}, {}, {}, {}, {}, {}, TDimFilter().SetAll(), Nothing(), Nothing()),
            {}, "uuid", NMonitoring::TLabels{}, statsManager, {}
        );
        allocator.Freeze();
        ExpectedSignals expected;
        expected.Counters.emplace_back("report-uuid-inprog", 0);
        expected.Counters.emplace_back("report-uuid-succ", 0);
        expected.Counters.emplace_back("report-uuid-fail", 0);
        expected.Counters.emplace_back("report-uuid-requests", 0);
        expected.Counters.emplace_back("report-uuid-conn_fail", 0);
        expected.Counters.emplace_back("report-uuid-backend_fail", 0);
        expected.Counters.emplace_back("report-uuid-client_fail", 0);
        expected.Counters.emplace_back("report-uuid-other_fail", 0);
        expected.Counters.emplace_back("report-uuid-ka", 0);
        expected.Counters.emplace_back("report-uuid-nka", 0);
        expected.Counters.emplace_back("report-uuid-reused", 0);
        expected.Counters.emplace_back("report-uuid-nreused", 0);
        expected.Counters.emplace_back("report-uuid-backend_keepalive_reused", 0);
        expected.Counters.emplace_back("report-uuid-conn_refused", 0);
        expected.Counters.emplace_back("report-uuid-conn_timeout", 0);
        expected.Counters.emplace_back("report-uuid-conn_other_error", 0);
        expected.Counters.emplace_back("report-uuid-client_timeout", 0);
        expected.Counters.emplace_back("report-uuid-backend_timeout", 0);
        expected.Counters.emplace_back("report-uuid-backend_attempt", 0);
        expected.Counters.emplace_back("report-uuid-limited_backend_attempt", 0);
        expected.Counters.emplace_back("report-uuid-backend_error", 0);
        expected.Counters.emplace_back("report-uuid-client_error", 0);
        expected.Counters.emplace_back("report-uuid-sc_503", 0);
        expected.Counters.emplace_back("report-uuid-input_speed", 0);
        expected.Counters.emplace_back("report-uuid-output_speed", 0);
        expected.Counters.emplace_back("report-uuid-hedged_attempts", 0);
        expected.Counters.emplace_back("report-uuid-hedged_succ", 0);
        expected.Counters.emplace_back("report-uuid-sc_1xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_1xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_2xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_2xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_3xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_3xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_4xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_4xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_5xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_5xx", 0);

        expected.Histograms.push_back({"report-uuid-2xx-u-u-cu", {{0, 2}, {0.01, 2}}});
        BaseTestRange(statsManager, record, expected);
    }

    void DoRecordMultiRangeTest() {
        using namespace NLegacyRange;
        TSharedAllocator allocator;
        TSharedStatsManager statsManager(allocator);
        statsManager.SetWorkersCount(0, 0);
        TRecordData record(
            TRecordRanges(TDurationRanges{"10ms,100ms"}, {}, {}, {}, {}, {}, {}, {}, TDimFilter().SetAll(), Nothing(), Nothing()),
            {}, "uuid", NMonitoring::TLabels{}, statsManager, {}
        );
        allocator.Freeze();
        ExpectedSignals expected;
        expected.Counters.emplace_back("report-uuid-inprog", 0);
        expected.Counters.emplace_back("report-uuid-succ", 0);
        expected.Counters.emplace_back("report-uuid-fail", 0);
        expected.Counters.emplace_back("report-uuid-requests", 0);
        expected.Counters.emplace_back("report-uuid-conn_fail", 0);
        expected.Counters.emplace_back("report-uuid-backend_fail", 0);
        expected.Counters.emplace_back("report-uuid-client_fail", 0);
        expected.Counters.emplace_back("report-uuid-other_fail", 0);
        expected.Counters.emplace_back("report-uuid-ka", 0);
        expected.Counters.emplace_back("report-uuid-nka", 0);
        expected.Counters.emplace_back("report-uuid-reused", 0);
        expected.Counters.emplace_back("report-uuid-nreused", 0);
        expected.Counters.emplace_back("report-uuid-backend_keepalive_reused", 0);
        expected.Counters.emplace_back("report-uuid-conn_refused", 0);
        expected.Counters.emplace_back("report-uuid-conn_timeout", 0);
        expected.Counters.emplace_back("report-uuid-conn_other_error", 0);
        expected.Counters.emplace_back("report-uuid-client_timeout", 0);
        expected.Counters.emplace_back("report-uuid-backend_timeout", 0);
        expected.Counters.emplace_back("report-uuid-backend_attempt", 0);
        expected.Counters.emplace_back("report-uuid-limited_backend_attempt", 0);
        expected.Counters.emplace_back("report-uuid-backend_error", 0);
        expected.Counters.emplace_back("report-uuid-client_error", 0);
        expected.Counters.emplace_back("report-uuid-sc_503", 0);
        expected.Counters.emplace_back("report-uuid-input_speed", 0);
        expected.Counters.emplace_back("report-uuid-output_speed", 0);
        expected.Counters.emplace_back("report-uuid-hedged_attempts", 0);
        expected.Counters.emplace_back("report-uuid-hedged_succ", 0);
        expected.Counters.emplace_back("report-uuid-sc_1xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_1xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_2xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_2xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_3xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_3xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_4xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_4xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_5xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_5xx", 0);

        expected.Histograms.push_back({"report-uuid-2xx-u-u-cu", {{0, 2}, {0.01, 1}, {0.1, 1}}});
        BaseTestRange(statsManager, record, expected);
    }

    void BaseBadStatusTest(const TSharedStatsManager& statsManager, TRecordData& record, const ExpectedSignals& expected) {
        using namespace NLegacyRange;
        TProcessMock process;
        TTestConnDescr descr(process);

        for (size_t i = 0; i < 30; i += 10) {
            for (size_t j = 0; j < 2; ++j) {
                for (auto status: {-1, 0, 600, 1100, 2000}) {
                    TLocalStats stats;
                    stats.TotalTime = TDuration::MilliSeconds(i + j);
                    stats.DimData = GetDimData(descr.ConnDescr(), status);
                    record.RegisterResponseEnd(stats); // will clamp to 500
                }
            }
        }

        auto stats = statsManager.GetBalancerStats();
        for (const auto& counter : expected.Counters) {
            auto c = std::find_if(stats.Counters.begin(), stats.Counters.end(), [&counter](const TCounter& cnt){
                return cnt.Name == counter.first;
            });
            UNIT_ASSERT_C(c != stats.Counters.end(), counter.first + " not found");
            UNIT_ASSERT_VALUES_EQUAL_C(c->Value, counter.second, counter.first + " value mismatch");
        }

        for (const auto& hist : expected.Histograms) {
            auto h = std::find_if(stats.Histograms.begin(), stats.Histograms.end(), [&hist](const THistogram& histogram){
                return histogram.Name == hist.first;
            });
            UNIT_ASSERT_C(h != stats.Histograms.end(), hist.first + " not found");
            UNIT_ASSERT_VALUES_EQUAL_C(h->Values, hist.second, hist.first + " values mismatch");
        }
    }

    void DoRecordBadStatusTestSingleBucket() {
        using namespace NLegacyRange;
        TSharedAllocator allocator;
        TSharedStatsManager statsManager(allocator);
        statsManager.SetWorkersCount(0, 0);
        TRecordData record(
            TRecordRanges(TDurationRanges("10ms"), {}, {}, {}, {}, {}, {}, {}, TDimFilter().SetAll(), Nothing(), Nothing()),
            {}, "uuid", NMonitoring::TLabels{}, statsManager, {}
        );
        allocator.Freeze();

        ExpectedSignals expected;
        expected.Counters.emplace_back("report-uuid-inprog", 0);
        expected.Counters.emplace_back("report-uuid-succ", 0);
        expected.Counters.emplace_back("report-uuid-fail", 0);
        expected.Counters.emplace_back("report-uuid-requests", 0);
        expected.Counters.emplace_back("report-uuid-conn_fail", 0);
        expected.Counters.emplace_back("report-uuid-backend_fail", 0);
        expected.Counters.emplace_back("report-uuid-client_fail", 0);
        expected.Counters.emplace_back("report-uuid-other_fail", 0);
        expected.Counters.emplace_back("report-uuid-ka", 0);
        expected.Counters.emplace_back("report-uuid-nka", 0);
        expected.Counters.emplace_back("report-uuid-reused", 0);
        expected.Counters.emplace_back("report-uuid-nreused", 0);
        expected.Counters.emplace_back("report-uuid-backend_keepalive_reused", 0);
        expected.Counters.emplace_back("report-uuid-conn_refused", 0);
        expected.Counters.emplace_back("report-uuid-conn_timeout", 0);
        expected.Counters.emplace_back("report-uuid-conn_other_error", 0);
        expected.Counters.emplace_back("report-uuid-client_timeout", 0);
        expected.Counters.emplace_back("report-uuid-backend_timeout", 0);
        expected.Counters.emplace_back("report-uuid-backend_attempt", 0);
        expected.Counters.emplace_back("report-uuid-limited_backend_attempt", 0);
        expected.Counters.emplace_back("report-uuid-backend_error", 0);
        expected.Counters.emplace_back("report-uuid-client_error", 0);
        expected.Counters.emplace_back("report-uuid-sc_503", 0);
        expected.Counters.emplace_back("report-uuid-input_speed", 0);
        expected.Counters.emplace_back("report-uuid-output_speed", 0);
        expected.Counters.emplace_back("report-uuid-hedged_attempts", 0);
        expected.Counters.emplace_back("report-uuid-hedged_succ", 0);
        expected.Counters.emplace_back("report-uuid-sc_1xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_1xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_2xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_2xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_3xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_3xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_4xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_4xx", 0);
        expected.Counters.emplace_back("report-uuid-sc_5xx", 0);
        expected.Counters.emplace_back("report-uuid-outgoing_5xx", 0);

        expected.Histograms.push_back({"report-uuid-err-u-u-cu", {{0, 10}, {0.01, 20}}});
        BaseBadStatusTest(statsManager, record, expected);
    }

    void DoRecordBadStatusTestMultipleBuckets() {
        using namespace NLegacyRange;
        TSharedAllocator allocator;
        TSharedStatsManager statsManager(allocator);
        statsManager.SetWorkersCount(0, 0);
        TRecordData multiRecord(
            TRecordRanges(TDurationRanges("10ms,100ms"), {}, {}, {}, {}, {}, {}, {}, TDimFilter().SetAll(), Nothing(), Nothing()),
            {}, "uuid2", NMonitoring::TLabels{}, statsManager, {}
        );
        allocator.Freeze();

        ExpectedSignals expected;
        expected.Counters.emplace_back("report-uuid2-inprog", 0);
        expected.Counters.emplace_back("report-uuid2-succ", 0);
        expected.Counters.emplace_back("report-uuid2-fail", 0);
        expected.Counters.emplace_back("report-uuid2-requests", 0);
        expected.Counters.emplace_back("report-uuid2-conn_fail", 0);
        expected.Counters.emplace_back("report-uuid2-backend_fail", 0);
        expected.Counters.emplace_back("report-uuid2-client_fail", 0);
        expected.Counters.emplace_back("report-uuid2-other_fail", 0);
        expected.Counters.emplace_back("report-uuid2-ka", 0);
        expected.Counters.emplace_back("report-uuid2-nka", 0);
        expected.Counters.emplace_back("report-uuid2-reused", 0);
        expected.Counters.emplace_back("report-uuid2-nreused", 0);
        expected.Counters.emplace_back("report-uuid2-backend_keepalive_reused", 0);
        expected.Counters.emplace_back("report-uuid2-conn_refused", 0);
        expected.Counters.emplace_back("report-uuid2-conn_timeout", 0);
        expected.Counters.emplace_back("report-uuid2-conn_other_error", 0);
        expected.Counters.emplace_back("report-uuid2-client_timeout", 0);
        expected.Counters.emplace_back("report-uuid2-backend_timeout", 0);
        expected.Counters.emplace_back("report-uuid2-backend_attempt", 0);
        expected.Counters.emplace_back("report-uuid2-limited_backend_attempt", 0);
        expected.Counters.emplace_back("report-uuid2-backend_error", 0);
        expected.Counters.emplace_back("report-uuid2-client_error", 0);
        expected.Counters.emplace_back("report-uuid2-sc_503", 0);
        expected.Counters.emplace_back("report-uuid2-input_speed", 0);
        expected.Counters.emplace_back("report-uuid2-output_speed", 0);
        expected.Counters.emplace_back("report-uuid2-hedged_attempts", 0);
        expected.Counters.emplace_back("report-uuid2-hedged_succ", 0);
        expected.Counters.emplace_back("report-uuid2-sc_1xx", 0);
        expected.Counters.emplace_back("report-uuid2-outgoing_1xx", 0);
        expected.Counters.emplace_back("report-uuid2-sc_2xx", 0);
        expected.Counters.emplace_back("report-uuid2-outgoing_2xx", 0);
        expected.Counters.emplace_back("report-uuid2-sc_3xx", 0);
        expected.Counters.emplace_back("report-uuid2-outgoing_3xx", 0);
        expected.Counters.emplace_back("report-uuid2-sc_4xx", 0);
        expected.Counters.emplace_back("report-uuid2-outgoing_4xx", 0);
        expected.Counters.emplace_back("report-uuid2-sc_5xx", 0);
        expected.Counters.emplace_back("report-uuid2-outgoing_5xx", 0);

        expected.Histograms.push_back({"report-uuid2-err-u-u-cu", {{0, 10}, {0.01, 20}, {0.1, 0}}});
        BaseBadStatusTest(statsManager, multiRecord, expected);
    }
}

Y_UNIT_TEST_SUITE(TModReportAuxUnitTest) {

    Y_UNIT_TEST(TStorageBaseTest) {
        using namespace NLegacyRange;
        TStorage storage(TDimFilter().Set(EDim::Status));

        UNIT_ASSERT(storage.Empty());

        TProcessMock process;
        TTestConnDescr descr(process);
        auto data = GetDimData(descr.ConnDescr(), 200);

        UNIT_ASSERT_EQUAL(storage.Get(data), 0);
        storage.Add(data);
        UNIT_ASSERT_EQUAL(storage.Get(data), 1);

        descr.ConnDescr().Properties->UserConnIsSsl = true;
        data = GetDimData(descr.ConnDescr(), 200);
        UNIT_ASSERT_EQUAL(storage.Get(data), 1);
        storage.Add(data);
        UNIT_ASSERT_EQUAL(storage.Get(data), 2);
    }

    Y_UNIT_TEST(StorageBaseFullTest) {
        using namespace NLegacyRange;
        TStorage storage(TDimFilter().SetAll());

        UNIT_ASSERT(storage.Empty());

        TProcessMock process;
        TTestConnDescr descr(process);
        auto data = GetDimData(descr.ConnDescr(), 200);

        UNIT_ASSERT_EQUAL(storage.Get(data), 0);
        storage.Add(data);
        UNIT_ASSERT_EQUAL(storage.Get(data), 1);

        descr.ConnDescr().Properties->UserConnIsSsl = true;
        data = GetDimData(descr.ConnDescr(), 200);
        UNIT_ASSERT_EQUAL(storage.Get(data), 0);
        storage.Add(data);
        UNIT_ASSERT_EQUAL(storage.Get(data), 1);
    }

    Y_UNIT_TEST(RecordTest) {
        DoRecordTest();
    }

    Y_UNIT_TEST(RecordWithInputSizesTest) {
        DoRecordWithInputSizesTest();
    }

    Y_UNIT_TEST(RecordWithOutputSizesTest) {
        DoRecordWithOutputSizesTest();
    }

    Y_UNIT_TEST(RecordWithBackendTimesTest) {
        DoRecordWithBackendTimesTest();
    }

    Y_UNIT_TEST(RecordWithClientFailTimesTest) {
        DoRecordWithClientFailTimesTest();
    }

    Y_UNIT_TEST(RecordWithHeadersSizeTest) {
        DoRecordWithHeadersSizeTest();
    }

    Y_UNIT_TEST(RecordSingleRangeTest) {
        DoRecordSingleRangeTest();
    }

    Y_UNIT_TEST(RecordMultiRangeTest) {
        DoRecordMultiRangeTest();
    }

    Y_UNIT_TEST(RecordBadStatusTestSingleBucket) {
        DoRecordBadStatusTestSingleBucket();
    }

    Y_UNIT_TEST(RecordBadStatusTestMultipleBuckets) {
        DoRecordBadStatusTestMultipleBuckets();
    }
}
