#include <maps/wikimap/mapspro/services/tasks_realtime/src/user_edits_metrics/lib/options.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/concurrent/include/scoped_guard.h>
#include <boost/algorithm/string/split.hpp>

namespace maps::wiki::user_edits_metrics {

namespace fs = std::filesystem;

namespace {

std::vector<double> parseQuantiles(const std::string& quantilesOpt)
{
    std::vector<std::string> splitQuantiles;
    boost::split(splitQuantiles, quantilesOpt, [](char c) { return c == ','; });
    std::vector<double> quantiles;
    quantiles.reserve(splitQuantiles.size());
    for (const auto& str: splitQuantiles) {
        double val = std::stod(str);
        REQUIRE(val >= 0 && val <= 1, "invalid quantile: " << val);
        quantiles.push_back(val);
    }
    return quantiles;
}

} // namespace

std::istream& operator>>(
    std::istream& is,
    Options::DeploymentDaysOutOfSlaOptions& ddsOptions)
{
    const auto flags = is.flags();
    maps::concurrent::ScopedGuard guard([&] { is.flags(flags); });

    // `lexical_cast` that uses this function during command line
    // parsing, unsets `skipws` flag. Therefore, either all space
    // symbols has to be skipped manually or the flag has to be set
    // back.
    is.setf(std::ios_base::skipws);

    is >> ddsOptions.window;
    is >> ddsOptions.quantile;
    is >> ddsOptions.brokenSlaBudget;
    return is;
}

Options Options::parse(int argc, char** argv) {
    const auto default_config = "/etc/yandex/maps/wiki/services/services.xml"s;

    Options result;

    maps::cmdline::Parser parser;
    auto config = parser.file("config")
        .help("Services config filename. (default: '" + default_config + "')")
        .defaultValue(default_config);
    auto window = parser.option<std::chrono::seconds>("window")
        .help("Window duration. (default: 24h)")
        .defaultValue(std::chrono::hours(24));
    auto singleMetricDepth = parser.size_t("single-metric-depth")
        .help("How many windows deep a single metric must be calculated "
              "starting from the first non-null value. (default: 40)")
        .defaultValue(40);
    auto totalDepth = parser.size_t("total-depth")
        .help("How many windows deep all metrics must be calculated. (default: 60)")
        .defaultValue(60);
    auto quantilesOpt = parser.string("quantiles")
        .help("Comma-separated list of quantiles to calculate (0 <= q <= 1). "
              "(default: 0.5,0.85,0.95)")
        .defaultValue("0.5,0.85,0.95");
    auto dumpDir = parser.dir("dump-dir")
        .help("Directory to dump metrics in CSV format. Do not dump by default.");
    auto noUpload = parser.flag("no-upload")
        .help("Do not upload collected data to Stat db.")
        .defaultValue(false);

    auto date = parser.string("date")
        .help("Date from which to past we consider total-depth windows for metrics."
              "Default is today")
        .defaultValue(maps::chrono::formatIsoDate(maps::chrono::TimePoint::clock::now()));

    parser.section("'Deployment days out of SLA' (DDS) and 'Achievable SLA' metrics");
    auto ddsOptions = parser.option<DeploymentDaysOutOfSlaOptionsVec>("dds")
        .metavar("'<window> <quantile> <budget>' ...")
        .help(
            "window   Sliding window size to calculate the metric.\n"
            "quantile Quantile to calculate the metric.\n"
            "budget   Maximum number of days in the window allowed to be out of SLA.\n"
            "(default: '30 0.85 5', '30 0.95 5', '10 0.85 2', '10 0.95 2')\n"
            "This option can be repeated several times."
        )
        .defaultValue(DeploymentDaysOutOfSlaOptionsVec{
            {30, 0.85, 5},
            {30, 0.95, 5},
            {10, 0.85, 2},
            {10, 0.95, 2}
        });

    parser.parse(argc, argv);

    std::vector<double> quantiles = parseQuantiles(quantilesOpt);

    for (const auto& opt: ddsOptions) {
        REQUIRE(
            opt.window <= singleMetricDepth,
            "Deployment days out of SLA window (" << opt.window << ") "
            "cannot be greater than a single metric depth (" << singleMetricDepth << ").");
    }

    std::optional<fs::path> dumpDirOpt;
    if (!dumpDir.empty()) {
        fs::path dumpDirPath(dumpDir.c_str());
        REQUIRE(fs::exists(dumpDirPath),
                "Dump directory '" << dumpDir << "' does not exist.");
        REQUIRE(fs::is_directory(dumpDirPath),
                "Dumps storage '" << dumpDir << "' is not a directory.");
        dumpDirOpt = dumpDirPath;
    }

    return {
        config,
        window,
        singleMetricDepth,
        totalDepth,
        quantiles,
        ddsOptions,
        dumpDirOpt,
        maps::chrono::parseIsoDate(date),
        !noUpload
    };
}

} // namespace maps::wiki::user_edits_metrics
