#pragma once

#include "record_data.h"

#include <balancer/modules/report/lib/uuid_parser.h>

#include <balancer/kernel/custom_io/stream.h>
#include <balancer/kernel/helpers/common_parsers.h>
#include <balancer/kernel/matcher/matcher.h>
#include <balancer/kernel/module/module.h>

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

#include <util/string/escape.h>
#include <util/string/subst.h>
#include <util/generic/ptr.h>
#include <util/string/cast.h>

namespace NReport {
    extern const TString DURATION_RANGES;
    extern const TString INPUT_RANGES;
    extern const TString OUTPUT_RANGES;
    extern const TString INPUT_HEADERS_RANGES;

    template <class TFunc>
    void CheckParse(TStringBuf key, TFunc&& func) {
        try {
            func();
        } catch (...) {
            ythrow TConfigParseError() << "error parsing \"" << key << "\": " << CurrentExceptionMessage();
        }
    }

    template <class TModule>
    struct TParser : public TModuleParams, public IConfig::IFunc {
        explicit TParser(TModule *parent) noexcept
                : TModuleParams(*parent), Parent(parent) {}

        void AssertNoDefault(TStringBuf key) {
            Y_ENSURE_EX(!DefaultRanges || !*DefaultRanges,
                        TConfigParseError() << "default is already set for " << key
                                            << "in option all_default_ranges");
        }

        void SetDefaultValues() {
            if (Uuids.empty()) {
                Uuids.emplace_back(); // If no uuid's were specified, we use the default one (empty string)
            }

            if (DefaultRanges && *DefaultRanges) {
                if (!LegacyTimeRanges.Defined()) {
                    LegacyTimeRanges.ConstructInPlace(DURATION_RANGES);
                }
                if (!BackendTimeRanges.Defined()) {
                    BackendTimeRanges.ConstructInPlace(DURATION_RANGES);
                }
                if (!ClientFailTimeRanges.Defined()) {
                    ClientFailTimeRanges.ConstructInPlace(DURATION_RANGES);
                }
                if (!FirstByteTimeRanges.Defined()) {
                    FirstByteTimeRanges.ConstructInPlace(DURATION_RANGES);
                }
                if (!InputSizeRanges.Defined()) {
                    InputSizeRanges.ConstructInPlace(INPUT_RANGES);
                }
                if (!OutputSizeRanges.Defined()) {
                    OutputSizeRanges.ConstructInPlace(OUTPUT_RANGES);
                }
                if (!InputHeadersSizeRanges.Defined()) {
                    InputHeadersSizeRanges.ConstructInPlace(INPUT_HEADERS_RANGES);
                }
            }

            OutgoingCodes.insert(101);
            OutgoingCodes.insert(404);
        }

        START_PARSE {
            if (key == "all_default_ranges") {
                Y_ENSURE_EX(!DefaultRanges, TConfigParseError() << "default is already set");
                DefaultRanges = FromString<bool>(value->AsString());
                return;
            }

            if (key == "uuid") {
                CheckParse(key, [&]() {
                    const TString uuids = value->AsString();
                    for (const auto &i: StringSplitter(uuids).Split(',')) {
                        Uuids.push_back(CheckedUuid(ToString(i.Token())));
                    }
                });
                return;
            }

            if (key == "outgoing_codes") {
                CheckParse(key, [&]() {
                    Y_ENSURE_EX(!OutgoingCodes, TConfigParseError() << "outgoing_codes already set");
                    for (auto code : StringSplitter(value->AsString()).Split(',')) {
                        OutgoingCodes.insert(FromString<unsigned>(code));
                    }
                });
                return;
            }

            if (key == "ranges") {
                CheckParse(key, [&]() {
                    if (value->AsString() == "default") {
                        AssertNoDefault(key);
                        DefaultRanges = false;
                        LegacyTimeRanges.ConstructInPlace(DURATION_RANGES);
                        return;
                    }
                    LegacyTimeRanges.ConstructInPlace(value->AsString());
                    if (!FirstByteTimeRanges.Defined()) {
                        FirstByteTimeRanges.ConstructInPlace(value->AsString());
                    }
                });
                return;
            }

            if (key == "ttfb_ranges") {
                CheckParse(key, [&]() {
                    if (value->AsString() == "default") {
                        AssertNoDefault(key);
                        DefaultRanges = false;
                        FirstByteTimeRanges.ConstructInPlace(DURATION_RANGES);
                        return;
                    }
                    FirstByteTimeRanges.ConstructInPlace(value->AsString());
                });
                return;
            }

            if (key == "backend_time_ranges") {
                CheckParse(key, [&]() {
                    if (value->AsString() == "default") {
                        AssertNoDefault(key);
                        DefaultRanges = false;
                        BackendTimeRanges.ConstructInPlace(DURATION_RANGES);
                        return;
                    }
                    BackendTimeRanges.ConstructInPlace(value->AsString());
                });
                return;
            }

            if (key == "client_fail_time_ranges") {
                CheckParse(key, [&]() {
                    if (value->AsString() == "default") {
                        AssertNoDefault(key);
                        DefaultRanges = false;
                        ClientFailTimeRanges.ConstructInPlace(DURATION_RANGES);
                        return;
                    }
                    ClientFailTimeRanges.ConstructInPlace(value->AsString());
                });
                return;
            }

            if (key == "input_size_ranges") {
                CheckParse(key, [&]() {
                    if (value->AsString() == "default") {
                        AssertNoDefault(key);
                        DefaultRanges = false;
                        InputSizeRanges.ConstructInPlace(INPUT_RANGES);
                        return;
                    }
                    InputSizeRanges.ConstructInPlace(value->AsString());
                });
                return;
            }

            if (key == "output_size_ranges") {
                CheckParse(key, [&]() {
                    if (value->AsString() == "default") {
                        AssertNoDefault(key);
                        DefaultRanges = false;
                        OutputSizeRanges.ConstructInPlace(OUTPUT_RANGES);
                        return;
                    }
                    OutputSizeRanges.ConstructInPlace(value->AsString());
                });
                return;
            }

            if (key == "input_headers_size_ranges") {
                CheckParse(key, [&]() {
                    if (value->AsString() == "default") {
                        AssertNoDefault(key);
                        DefaultRanges = false;
                        InputHeadersSizeRanges.ConstructInPlace(INPUT_HEADERS_RANGES);
                        return;
                    }
                    InputHeadersSizeRanges.ConstructInPlace(value->AsString());
                });
                return;
            }

            if (key == "matcher_map") {
                CheckParse(key, [&]() {
                    ParseMap(value->AsSubConfig(), [this, &m = Label2MatcherMap](auto key, auto *value) {
                        ParseMap(value->AsSubConfig(), [&](auto subKey, auto *subValue) {
                            Y_ENSURE_EX(!m.contains(key),
                                        TConfigParseError() << "duplicate matchers for label " << key.Quote());
                            m[key] = ConstructRequestMatcher(Parent->Control, subKey, subValue->AsSubConfig());
                        });
                    });
                });
                return;
            }

            if (key == "labels") {
                CheckParse(key, [&]() {
                    ParseMap(value->AsSubConfig(), [&l = Labels](auto key, auto *value) {
                        Y_ENSURE_EX(!l.Has(key),
                                    TConfigParseError() << "duplicate label " << key.Quote());
                        l.Add(key, value->AsString());
                    });
                });
                return;
            }

            bool val;
            ON_KEY("disable_robotness", val) {
                if (DisableRobotness.Defined() && *DisableRobotness != val) {
                    ythrow TConfigParseError{} << "inconsistent use of disable_robotness";
                }
                DisableRobotness = val;

                if (val) {
                    DimFilter.Reset(NLegacyRange::EDim::Robot);
                    DimFilter.Reset(NLegacyRange::EDim::Internal);
                }
                return;
            }

            ON_KEY("disable_sslness", val) {
                if (DisableSslness.Defined() && *DisableSslness != val) {
                    ythrow TConfigParseError{} << "inconsistent use of disable_sslness";
                }
                DisableSslness = val;

                if (val) {
                    DimFilter.Reset(NLegacyRange::EDim::Ssl);
                }
                return;
            }

            if (key == "disable_signals") {
                if (!DisabledSignals.empty()) {
                    ythrow TConfigParseError{} << "inconsistent use of disable_signals";
                }
                DisabledSignals = StringSplitter(value->AsString()).Split(',').SkipEmpty();
                CheckSignalsConsistence();
                return;
            }

            if (key == "enable_signals") {
                if (!EnabledSignals.empty()) {
                    ythrow TConfigParseError{} << "inconsistent use of enable_signals";
                }
                EnabledSignals = StringSplitter(value->AsString()).Split(',').SkipEmpty();
                CheckSignalsConsistence();
                return;
            }

            if (key == "signal_set") {
                SignalSet = FromString<ESignalSet>(value->AsString());
                return;
            }

            return Parent->Consume(key, value);
        } END_PARSE

        void CheckSignalsConsistence() const {
            for (const auto& signal : EnabledSignals) {
                if (DisabledSignals.contains(signal)) {
                    ythrow TConfigParseError{} << "inconsistent use of disable_signals and enable_signals";
                }
            }
        }

        TModule *const Parent = nullptr;
        TMaybe<TDurationRanges> LegacyTimeRanges;
        TMaybe<TDurationRanges> BackendTimeRanges;
        TMaybe<TDurationRanges> ClientFailTimeRanges;
        TMaybe<TDurationRanges> FirstByteTimeRanges;
        TMaybe<TSizeRanges> InputSizeRanges;
        TMaybe<TSizeRanges> OutputSizeRanges;
        TMaybe<TSizeRanges> InputHeadersSizeRanges;
        TMaybe<bool> DefaultRanges;
        TVector<TString> Uuids;
        NMonitoring::TLabels Labels;
        THashMap<TString, THolder<IRequestMatcher>> Label2MatcherMap;
        TCodesSet OutgoingCodes;
        TMaybe<bool> DisableSslness;
        TMaybe<bool> DisableRobotness;
        NLegacyRange::TDimFilter DimFilter = NLegacyRange::TDimFilter().SetAll();
        THashSet<TString> DisabledSignals;
        THashSet<TString> EnabledSignals;
        ESignalSet SignalSet = ESignalSet::Default;
    };
}

