#include "backend_info_helper.h"
#include "pinger2.h"
#include <balancer/kernel/pinger/common/stats.h>
#include <balancer/kernel/http/parser/common_headers.h>

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

namespace NSrvKernel {

TPingerV2::TPingerV2(IPingableV2& backend, const TPingerConfig& config, IWorkerCtl& process, bool useDynamicWeight, bool useDegradationNotification, bool useBackendsGrouping)
    : Backend_(backend)
    , Config_(config)
    , Process_(process)
    , UseDynamicWeight_(useDynamicWeight)
    , UseDegradationNotification_(useDegradationNotification)
    , UseBackendsGrouping_(useBackendsGrouping)
{
}

constexpr TStringBuf RS_DEGRADED = "RS-Degraded";
constexpr TStringBuf RS_WEIGHT = "RS-Weight";
constexpr TStringBuf RS_GROUP = "RS-Group";

void TPingerV2::ProcessPingResult(const TBackendStatus& status) {
    Backend_.SetEnabled(status.IsAlive);
    Backend_.SetWeightFromPing(status.Weight, status.IsWeightFromBackend);
    Backend_.SetDegraded(status.Degraded);
    if (status.IsAlive) {
        Backend_.SetGroupName(status.GroupName);
    }
}

TBackendStatus TPingerV2::BackendStatusFromResponse(TResponse &response, double default_weight) noexcept {
    bool degraded = false;
    if (UseDegradationNotification_) {
        if (auto value = response.Headers().GetFirstValue(RS_DEGRADED)) {
            degraded = Match(TTrueFsm::Instance(), value);
        }
    }

    if (degraded) {
        Counters_->DegradedPingsCounter.Inc();
    }

    TString groupName;
    if (UseBackendsGrouping_) {
        if (auto value = response.Headers().GetFirstValue(RS_GROUP)) {
            groupName = value;
        }
    }

    if (UseDynamicWeight_) {
        if (auto value = response.Headers().GetFirstValue(RS_WEIGHT)) {
            double weight = 0;
            if (TryFromString(value.data(), value.size(), weight)) {
                if (weight == 0) {
                    Counters_->ZeroWeightPingsCounter.Inc();
                }

                return TBackendStatus{true, weight, true, degraded, std::move(groupName)};
            }
        }

        if (Counters_) {
            Counters_->ParseFailedPingsCounter.Inc();
        }
        return TBackendStatus{true, default_weight, false, degraded, std::move(groupName)};
    }

    return TBackendStatus{true, 1, false, degraded, std::move(groupName)};
}

TBackendStatus TPingerV2::Ping() noexcept {
    TAsyncRequester requester(*Backend_.ActiveCheckModule(), nullptr, Process_);
    requester.Props().SkipKeepalive = true;
    bool ok = false;
    TResponse response;

    const ui64 startOfPing = Now().MicroSeconds();

    Y_TRY(TError, error) {
        if (Counters_) {
            Counters_->TotalPingsCounter.Inc();
        }
        if (Config_.PingRequest) {
            Y_PROPAGATE_ERROR(requester.Requester().Request(TRequest(*Config_.PingRequest), response));
            ok = response.ResponseLine().StatusCode == HTTP_OK;
        } else {
            TNullStream in;
            TNullStream out;
            Y_PROPAGATE_ERROR(requester.Requester().TcpRequest(in, out));
            ok = true;
        }
        if (Counters_) {
            Counters_->DelayOfPingsHistogram.AddValue(Now().MicroSeconds() - startOfPing);
        }
        return {};
    } Y_CATCH {
        if (Counters_) {
            Counters_->DelayOfPingsHistogram.AddValue(Now().MicroSeconds() - startOfPing);
            Counters_->ConnectionFailedPingsCounter.Inc();
            // TODO(velavokr):
            if (ErrorIsConnRefused(error)) {
                Counters_->ConnectionRefusedPingsCounter.Inc();
            } else if (ErrorHasErrno(error, {ETIMEDOUT})) {
                Counters_->ConnectionTimeoutPingsCounter.Inc();
            } else {
                Counters_->ConnectionOtherErrorPingsCounter.Inc();
            }
        }
    }

    if (Counters_) {
        if (ok) {
            Counters_->SuccessfulPingsCounter.Inc();
        } else {
            Counters_->BadStatusPingsCounter.Inc();
        }
    }
    if (ok) {
        return BackendStatusFromResponse(response, Backend_.OriginalWeight());
    }
    return TBackendStatus{false, 1, false, false, {}};
}

void TPingerV2::LoadCounters(TPingerStatsCounters* counters) noexcept {
    Counters_ = counters;
}

}  // namespace NSrvKernel
