#include "config_check.h"
#include "master_main.h"

#include <balancer/kernel/fs/kv_file_consumer.h>
#include <library/cpp/coroutine/engine/impl.h>
#include <util/random/shuffle.h>

namespace {
    template <typename T>
    bool TryBackendCheckParametersPartFromString(TMaybe<T>& part, const TString& stringRep) {
        if (!stringRep || stringRep == "-") {
            return true;
        }
        T val;
        if (TryFromString(stringRep, val)) {
            part = val;
            return true;
        }
        return false;
    }
}

template <>
NSrvKernel::TBackendCheckParameters FromStringImpl<NSrvKernel::TBackendCheckParameters, char>(const char* data, size_t size) {
    NSrvKernel::TBackendCheckParameters parameters;
    if (size > 0) {
        TVector<TString> parts = StringSplitter(data, size).Split(',');
        bool failed = false;
        switch (parts.size()) {
            case 4:
                failed |= !TryBackendCheckParametersPartFromString(parameters.AmountHysteresis, parts[3]);
                [[fallthrough]];
            case 3:
                failed |= !TryBackendCheckParametersPartFromString(parameters.Hysteresis, parts[2]);
                [[fallthrough]];
            case 2:
                failed |= !TryBackendCheckParametersPartFromString(parameters.AmountQuorum, parts[1]);
                [[fallthrough]];
            case 1:
                failed |= !TryBackendCheckParametersPartFromString(parameters.Quorum, parts[0]);
                break;
            default:
                failed = true;
        }
        if (failed) {
            ythrow TFromStringException{} << '"' << TStringBuf{data, size} << '"'
                                          << " is not a valid TBackendCheckParameters";
        }
    }
    return parameters;
}

namespace NSrvKernel::NProcessCore {

    namespace {
        void PrintQuorumOverrides(const TMaybe<TBackendCheckParameters>& overridenParameters) noexcept {
            if (overridenParameters && (overridenParameters->Quorum || overridenParameters->AmountQuorum)) {
                auto& log = Y_LOG_STDERR("QUORUM") << overridenParameters->SectionName << ",";
                if (overridenParameters->Quorum) {
                    log << *overridenParameters->Quorum << ",";
                } else {
                    log << "-,";
                }
                if (overridenParameters->AmountQuorum) {
                    log << *overridenParameters->AmountQuorum;
                } else {
                    log << "-";
                }
                log << Endl;
            }
        }
    }

    THolder<TCoroutine> TConfigCheck::Start(IWorkerCtl& ctl, TVector<TBackendGroup>& groups) {
        Shuffle(groups.begin(), groups.end());
        return MakeHolder<TCoroutine>(ECoroType::Common, "config_check_task", &ctl.Executor(), [this, &ctl, &groups] () {
            if (!MainTask->CheckBackends()) {
                MainTask->StopMaster({});
                return;
            }

            if (FileName && !TFsPath(FileName).Exists()) {
                Y_LOG_STDERR("INFO") << "quorums_file " << FileName << " not found" << Endl;
            }

            THashSet<TString> checked;

            bool failed = false;
            for (auto&& g : groups) {
                if (SkipSameGroups && !checked.insert(g.Name).second) {
                    Y_LOG_STDERR("SKIPPED") << g.Name << " (already checked)" << Endl;
                    continue;
                }

                Y_LOG_STDERR("CHECK") << g.Name << Endl;

                TMaybe<TBackendCheckParameters> overridenParameters;
                const auto& overrides = ctl.BackendCheckOverrides();
                if (auto ptr = overrides.Sections.FindPtr(g.Name)) {
                    overridenParameters = *ptr;
                } else if (overrides.Global) {
                    overridenParameters = overrides.Global;
                }
                PrintQuorumOverrides(overridenParameters);

                auto status = g.Mod->CheckBackends(ctl, false);

                for (auto&& err : status.Errors) {
                    Y_LOG_STDERR("ERROR") << g.Name << " = " << GetErrorMessage(err) << Endl;
                }
                if (status.Status == TBackendCheckResult::EStatus::Failed) {
                    failed = true;
                }
                Y_LOG_STDERR(TString(TStringBuilder() << status.Status)) << g.Name << Endl;
            }
            if (failed) {
                Y_LOG_STDERR("FAILED") << "Config check failed" << Endl;
                MainTask->ShutdownWithError("Config check failed");
            } else {
                Y_LOG_STDERR("PASSED") << "Config check passed" << Endl;
                MainTask->StopMaster({});
            }
        });
    }

    TBackendCheckOverridesReReader::TBackendCheckOverridesReReader(IWorkerCtl& process, const TString& fileName) {
        if (fileName) {
            ReReader_ = process.SharedFiles()->FileReReader(fileName, TDuration::Seconds(1));
        }
    }

    const TBackendCheckOverridenParameters& TBackendCheckOverridesReReader::Current() {
        const auto& data = ReReader_.Data();
        if (Overrides_.Id != data.Id()) {
            Overrides_.Id = data.Id();
            TKvFileConsumer<TBaseFileReaction<TString, TBackendCheckParameters>> consumer;
            ProcessKvData(consumer, data.Data());
            Overrides_.Sections = std::move(consumer.TakeStorage());
            for (auto& sec : Overrides_.Sections) {
                sec.second.SectionName = sec.first;
            }
            if (auto ptr = Overrides_.Sections.FindPtr("*")) {
                Overrides_.Global = *ptr;
            } else {
                Overrides_.Global.Clear();
            }
        }
        return Overrides_;
    }
}
