#include <maps/wikimap/mapspro/services/tasks_realtime/src/user_edits_metrics/lib/st_notifier.h>
#include <maps/wikimap/mapspro/services/tasks_realtime/src/user_edits_metrics/tests/small_tests/metric_creator.h>

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

namespace maps::wiki::user_edits_metrics::tests {

namespace {

NotifySettings getTestSettings()
{
    NotifySettings result;
    result.quantile = 0.85;
    result.inTrunk = true;
    result.region = "cis1";
    result.userType = USER_TYPE_COMMON;

    result.windowSize = 10;
    result.sla = 5_days;
    result.slaWarningLevel = 3_days;

    result.queueKey = "MAPSLSR";
    result.summary = "Broken SLA (Total) warning";
    result.referenceToGraphics = "https://datalens.yandex-team.ru/bq3fxpmya6e67-kpi?tab=yd0";
    result.followers = {"alexbobkov", "tail"};
    return result;
}

} // namespace

class IssueMock
{
public:
    IssueMock(const std::string& summary, const std::string& description, const std::string& tag)
        : summary_(summary)
        , description_(description)
        , tag_(tag)
    {}

    static std::string key()
    {
        return {};
    }

    std::unordered_set<std::string> tags() const
    {
        return {tag_};
    }

    std::string description() const
    {
        return description_;
    }

    std::string summary_;
    std::string description_;
    std::string comments_;
    std::string tag_;
};

class StAgentMock
{
public:
    StAgentMock(const std::optional<IssueMock>& issueMock): issueMock_(issueMock) {}

    IssueMock createIssue(
        const std::string& summary,
        const std::string& description,
        const std::unordered_set<std::string>&) const
    {
        return IssueMock(summary, description, "");
    }

    std::optional<IssueMock> findOpenedIssue(const std::string& summary) const
    {
        if (issueMock_.has_value() && issueMock_->summary_ == summary) {
            return issueMock_;
        } else {
            return {};
        }
    }

    void updateIssue(
        const IssueMock& issue,
        const std::string& comments,
        std::optional<chrono::TimePoint> newTagTime) const
    {
        issueMock_ = issue;
        issueMock_->comments_ = comments;
        issueMock_->tag_ = (newTagTime) ? maps::chrono::formatIsoDate(newTagTime.value()) : issueMock_->tag_;
    }

    mutable std::optional<IssueMock> issueMock_;
};


Y_UNIT_TEST_SUITE(st_notifier_test)
{

Y_UNIT_TEST(test_notify_settings)
{
    NotifySettings settings = getTestSettings();
    UNIT_ASSERT_VALUES_EQUAL(settings.quantile, 0.85);
    UNIT_ASSERT_VALUES_EQUAL(settings.inTrunk, true);
    UNIT_ASSERT_VALUES_EQUAL(settings.region, "cis1");
    UNIT_ASSERT_VALUES_EQUAL(settings.userType, USER_TYPE_COMMON);

    UNIT_ASSERT_VALUES_EQUAL(settings.windowSize, 10);
    UNIT_ASSERT_VALUES_EQUAL(settings.sla.count(), (5_days).count());
    UNIT_ASSERT_VALUES_EQUAL(settings.slaWarningLevel.count(), (3_days).count());

    UNIT_ASSERT_VALUES_EQUAL(settings.queueKey, "MAPSLSR");
    UNIT_ASSERT_VALUES_EQUAL(settings.summary, "Broken SLA (Total) warning");
    UNIT_ASSERT_VALUES_EQUAL(
        settings.referenceToGraphics,
        "https://datalens.yandex-team.ru/bq3fxpmya6e67-kpi?tab=yd0"
    );
    std::unordered_set<std::string> expectedFollowers = {"alexbobkov", "tail"};
    UNIT_ASSERT_VALUES_EQUAL(settings.followers.size(), expectedFollowers.size());
    for (const auto& follower: expectedFollowers) {
        UNIT_ASSERT(settings.followers.count(follower));
    }
}

Y_UNIT_TEST(test_metric_prepare)
{
    NotifySettings settings = getTestSettings();
    settings.deployedEvents = {
        Event::DeployedCams,
        Event::DeployedGraph
    };
    MetricPrepare metricPrepare(settings);

    auto baseMetricCreator = MetricCreator().userType(settings.userType).region(settings.region).
        inTrunk(settings.inTrunk).quantile(settings.quantile);
    auto camsMetricCreator = baseMetricCreator.event(Event::DeployedCams);
    auto graphMetricCreator = baseMetricCreator.event(Event::DeployedGraph);

    MetricVec quantileMetrics{
        camsMetricCreator.time("2019-01-14 11:00:00").value(4_days)(),
        graphMetricCreator.time("2019-01-14 11:00:00").value(6_days)(),
        camsMetricCreator.time("2019-01-15 11:00:00").value(1_days)(),
        graphMetricCreator.time("2019-01-15 11:00:00").value(1_days)(),
        camsMetricCreator.time("2019-01-16 11:00:00").value(1_days)(),
        graphMetricCreator.time("2019-01-16 11:00:00").value(8_days)(),
        camsMetricCreator.time("2019-01-17 11:00:00").value(1_days)(),
        graphMetricCreator.time("2019-01-17 11:00:00").value(1_days)(),
        camsMetricCreator.time("2019-01-18 11:00:00").value(1_days)(),
        graphMetricCreator.time("2019-01-18 01:00:00").value(1_days)(),
        camsMetricCreator.time("2019-01-19 11:00:00").value(1_days)(),
        graphMetricCreator.time("2019-01-19 11:00:00").value(1_days)(),
        camsMetricCreator.time("2019-01-20 11:00:00").value(1_days)(),
        graphMetricCreator.time("2019-01-20 11:00:00").value(1_days)(),
        camsMetricCreator.time("2019-01-21 11:00:00").value(1_days)(),
        graphMetricCreator.time("2019-01-21 11:00:00").value(1_days)(),
        camsMetricCreator.time("2019-01-22 11:00:00").value(1_days)(),
        graphMetricCreator.time("2019-01-22 11:00:00").value(1_days)(),
        camsMetricCreator.time("2019-01-23 11:20:00").value(1_days)(),
        graphMetricCreator.time("2019-01-23 11:00:00").value(5_days)(),
        camsMetricCreator.time("2019-01-24 11:00:00").value(1_days)(),
        graphMetricCreator.time("2019-01-24 11:00:00").value(1_days)(),
        camsMetricCreator.time("2019-01-25 10:00:00").value(5_days + 1min)(),
        graphMetricCreator.time("2019-01-25 11:00:00").value(1_days)(),
        camsMetricCreator.time("2019-01-26 11:00:00").value(6_days)(),
        graphMetricCreator.time("2019-01-26 11:00:00").value(5_days)()
    };
    const auto calculationDate = chrono::parseSqlDateTime("2019-01-31 00:00:00");

    const auto brokenSlaTotalMetrics = metricPrepare.calcBrokenSlaTotalMetrics(
        quantileMetrics,
        calculationDate
    );

    auto totalBrokenSlaMetricCreator = baseMetricCreator.event(Event::DeployedTotalBrokenSla);
    MetricVec expectingBrokenSlaTotalMetrics{
        totalBrokenSlaMetricCreator.time("2019-01-23 00:00:00").value(2_days)(),
        totalBrokenSlaMetricCreator.time("2019-01-24 00:00:00").value(1_days)(),
        totalBrokenSlaMetricCreator.time("2019-01-25 00:00:00").value(2_days)(),
        totalBrokenSlaMetricCreator.time("2019-01-26 00:00:00").value(2_days)()
    };

    UNIT_ASSERT_VALUES_EQUAL(brokenSlaTotalMetrics.size(), expectingBrokenSlaTotalMetrics.size());
    for (size_t i = 0; i < expectingBrokenSlaTotalMetrics.size(); ++i) {
        UNIT_ASSERT_VALUES_EQUAL_C(
            brokenSlaTotalMetrics[i].time.time_since_epoch().count(),
            expectingBrokenSlaTotalMetrics[i].time.time_since_epoch().count(),
            "i = " + std::to_string(i)
        );
        UNIT_ASSERT_VALUES_EQUAL_C(
            brokenSlaTotalMetrics[i].event,
            expectingBrokenSlaTotalMetrics[i].event,
            "i = " + std::to_string(i)
        );
        UNIT_ASSERT_VALUES_EQUAL_C(
            brokenSlaTotalMetrics[i].value.count(),
            expectingBrokenSlaTotalMetrics[i].value.count(),
            "i = " + std::to_string(i)
        );
    }

    const auto brokenSlaMetrics = metricPrepare.calcBrokenSlaMetrics(
        quantileMetrics,
        calculationDate
    );

    auto camsBrokenSlaMetricCreator = baseMetricCreator.event(Event::DeployedCamsBrokenSla);
    auto graphBrokenSlaMetricCreator = baseMetricCreator.event(Event::DeployedGraphBrokenSla);
    std::map<Event, MetricVec> expectingBrokenSlaMetrics{
        {
            Event::DeployedCams, {
                camsBrokenSlaMetricCreator.time("2019-01-23 00:00:00").value(0_days)(),
                camsBrokenSlaMetricCreator.time("2019-01-24 00:00:00").value(0_days)(),
                camsBrokenSlaMetricCreator.time("2019-01-25 00:00:00").value(1_days)(),
                camsBrokenSlaMetricCreator.time("2019-01-26 00:00:00").value(2_days)()
            }
        },
        {
            Event::DeployedGraph, {
                graphBrokenSlaMetricCreator.time("2019-01-23 00:00:00").value(2_days)(),
                graphBrokenSlaMetricCreator.time("2019-01-24 00:00:00").value(1_days)(),
                graphBrokenSlaMetricCreator.time("2019-01-25 00:00:00").value(1_days)(),
                graphBrokenSlaMetricCreator.time("2019-01-26 00:00:00").value(0_days)()
            }
        }
    };

    UNIT_ASSERT_VALUES_EQUAL(brokenSlaMetrics.size(), settings.deployedEvents.size());
    for (const auto event: settings.deployedEvents) {
        const auto& result = brokenSlaMetrics.at(event);
        const auto& expect = expectingBrokenSlaMetrics.at(event);
        for (size_t i = 0; i < expect.size(); ++i) {
            UNIT_ASSERT_VALUES_EQUAL_C(
                result[i].time.time_since_epoch().count(),
                expect[i].time.time_since_epoch().count(),
                "(i = " + std::to_string(i) + "; event = " + std::string(toString(event)) + ")"
            );
            UNIT_ASSERT_VALUES_EQUAL_C(
                result[i].event,
                expect[i].event,
                "(i = " + std::to_string(i) + "; event = " + std::string(toString(event)) + ")"
            );
            UNIT_ASSERT_VALUES_EQUAL_C(
                result[i].value.count(),
                expect[i].value.count(),
                "(i = " + std::to_string(i) + "; event = " + std::string(toString(event)) + ")"
            );
        }
    }
}

Y_UNIT_TEST(test_st_notifier)
{
    NotifySettings settings = getTestSettings();

    auto baseMetric = MetricCreator().region(settings.region).inTrunk(settings.inTrunk).userType(settings.userType);
    auto baseTotalMetric = baseMetric.event(Event::DeployedTotalBrokenSla);
    auto baseCarparksMetric = baseMetric.event(Event::DeployedCarparksBrokenSla);
    auto baseGraphMetric = baseMetric.event(Event::DeployedGraphBrokenSla);

    MetricVec brokenSlaTotal {
        baseTotalMetric.time("2019-01-15 00:00:00").value(1_days)(),
        baseTotalMetric.time("2019-01-16 00:00:00").value(2_days)(),
        baseTotalMetric.time("2019-01-17 00:00:00").value(3_days)(),
        baseTotalMetric.time("2019-01-18 00:00:00").value(4_days)(),
        baseTotalMetric.time("2019-01-19 00:00:00").value(3_days)()
    };

    MetricVec brokenSlaCarparks {
        baseCarparksMetric.time("2019-01-15 00:00:00").value(0_days)(),
        baseCarparksMetric.time("2019-01-16 00:00:00").value(1_days)(),
        baseCarparksMetric.time("2019-01-17 00:00:00").value(2_days)(),
        baseCarparksMetric.time("2019-01-18 00:00:00").value(2_days)(),
        baseCarparksMetric.time("2019-01-19 00:00:00").value(1_days)()
    };
    MetricVec brokenSlaGraph {
        baseGraphMetric.time("2019-01-15 00:00:00").value(1_days)(),
        baseGraphMetric.time("2019-01-16 00:00:00").value(1_days)(),
        baseGraphMetric.time("2019-01-17 00:00:00").value(1_days)(),
        baseGraphMetric.time("2019-01-18 00:00:00").value(2_days)(),
        baseGraphMetric.time("2019-01-19 00:00:00").value(2_days)()
    };

    std::map<Event, MetricVec> brokenSla = {
        {Event::DeployedCarparksBrokenSla, brokenSlaCarparks},
        {Event::DeployedGraphBrokenSla, brokenSlaGraph}
    };

    {
        // If issue was not created yet
        StNotifier notifier(settings, brokenSlaTotal, brokenSla);
        std::optional<IssueMock> notExistingIssue = {};
        StAgentMock stAgent(notExistingIssue);
        notifier.doNotifyLogic(stAgent);

        UNIT_ASSERT(stAgent.issueMock_.has_value());
        UNIT_ASSERT_VALUES_EQUAL(stAgent.issueMock_->summary_, settings.summary);
        UNIT_ASSERT_VALUES_EQUAL(stAgent.issueMock_->tag_, "2019-01-19"s);
        std::string expectingComments = {
            "2019-01-17\n"
            "Количество дней, в которых нарушался SLA, достигло значения 3\n"
            "Нарушений SLA **deployed_carparks_broken_sla** = 2\n"
            "Нарушений SLA **deployed_graph_broken_sla** = 1\n\n"
            "2019-01-18\n"
            "Количество дней, в которых нарушался SLA, увеличилось до 4\n"
            "Количество нарушений SLA **deployed_graph_broken_sla** достигло значения 2"
        };
        UNIT_ASSERT_VALUES_EQUAL(stAgent.issueMock_->comments_, expectingComments);
    }

    {
        // If issue was already created
        StNotifier notifier(settings, brokenSlaTotal, brokenSla);
        std::optional<IssueMock> existingIssue = IssueMock(
            settings.summary,
            "Some description",
            "2019-01-17"
        );
        StAgentMock stAgent(existingIssue);
        notifier.doNotifyLogic(stAgent);

        UNIT_ASSERT(stAgent.issueMock_.has_value());
        UNIT_ASSERT_VALUES_EQUAL(stAgent.issueMock_->summary_, settings.summary);
        UNIT_ASSERT_VALUES_EQUAL(stAgent.issueMock_->description_, "Some description"s);
        UNIT_ASSERT_VALUES_EQUAL(stAgent.issueMock_->tag_, "2019-01-19"s);
        const std::string expectingComment =
            "2019-01-18\n"
            "Количество дней, в которых нарушался SLA, увеличилось до 4\n"
            "Количество нарушений SLA **deployed_graph_broken_sla** достигло значения 2";
        UNIT_ASSERT_VALUES_EQUAL(stAgent.issueMock_->comments_, expectingComment);
    }
}

Y_UNIT_TEST(test_cut_off_untrusted_broken_sla_total)
{
    NotifySettings settings = getTestSettings();
    settings.deployedEvents = DEPLOYED_TO_PRODUCTION_EVENTS;
    auto baseMetric = MetricCreator().region(settings.region).inTrunk(settings.inTrunk).userType(settings.userType);

    MetricVec brokenSlaTotal {
        baseMetric.event(Event::DeployedTotalBrokenSla).time("2019-01-15 00:00:00")(),
        baseMetric.event(Event::DeployedTotalBrokenSla).time("2019-01-16 00:00:00")(),
        baseMetric.event(Event::DeployedTotalBrokenSla).time("2019-01-17 00:00:00")(),
        baseMetric.event(Event::DeployedTotalBrokenSla).time("2019-01-18 00:00:00")(),
        baseMetric.event(Event::DeployedTotalBrokenSla).time("2019-01-19 00:00:00")()
    };
    MetricVec brokenSlaBicycleGrap {
        baseMetric.event(Event::DeployedBicycleGraph).time("2019-01-15 00:00:00")(),
        baseMetric.event(Event::DeployedBicycleGraph).time("2019-01-16 00:00:00")(),
        baseMetric.event(Event::DeployedBicycleGraph).time("2019-01-17 00:00:00")()
    };
    MetricVec brokenSlaCams {
        baseMetric.event(Event::DeployedCams).time("2019-01-15 00:00:00")(),
        baseMetric.event(Event::DeployedCams).time("2019-01-16 00:00:00")(),
        baseMetric.event(Event::DeployedCams).time("2019-01-17 00:00:00")()
    };
    MetricVec brokenSlaCarparks {
        baseMetric.event(Event::DeployedCarparks).time("2019-01-15 00:00:00")(),
        baseMetric.event(Event::DeployedCarparks).time("2019-01-16 00:00:00")(),
        baseMetric.event(Event::DeployedCarparks).time("2019-01-17 00:00:00")(),
        baseMetric.event(Event::DeployedCarparks).time("2019-01-18 00:00:00")(),
    };
    MetricVec brokenSlaGeocoder {
        baseMetric.event(Event::DeployedGeocoder).time("2019-01-15 00:00:00")(),
        baseMetric.event(Event::DeployedGeocoder).time("2019-01-16 00:00:00")()
    };
    MetricVec brokenSlaGraph {
        baseMetric.event(Event::DeployedGraph).time("2019-01-15 00:00:00")(),
        baseMetric.event(Event::DeployedGraph).time("2019-01-16 00:00:00")(),
        baseMetric.event(Event::DeployedGraph).time("2019-01-17 00:00:00")()
    };
    MetricVec brokenSlaMtrExport {
        baseMetric.event(Event::DeployedMtrExport).time("2019-01-15 00:00:00")(),
        baseMetric.event(Event::DeployedMtrExport).time("2019-01-16 00:00:00")(),
        baseMetric.event(Event::DeployedMtrExport).time("2019-01-17 00:00:00")()
    };
    MetricVec brokenSlaPedestrianGraph {
        baseMetric.event(Event::DeployedPedestrianGraph).time("2019-01-15 00:00:00")(),
        baseMetric.event(Event::DeployedPedestrianGraph).time("2019-01-16 00:00:00")(),
        baseMetric.event(Event::DeployedPedestrianGraph).time("2019-01-17 00:00:00")()
    };
    MetricVec brokenSlaRenderer {
        baseMetric.event(Event::DeployedRenderer).time("2019-01-15 00:00:00")(),
        baseMetric.event(Event::DeployedRenderer).time("2019-01-16 00:00:00")(),
        baseMetric.event(Event::DeployedRenderer).time("2019-01-17 00:00:00")()
    };
    std::map<Event, MetricVec> brokenSla = {
        {Event::DeployedBicycleGraph, brokenSlaBicycleGrap},
        {Event::DeployedCams, brokenSlaCams},
        {Event::DeployedCarparks, brokenSlaCarparks},
        {Event::DeployedGeocoder, brokenSlaGeocoder},
        {Event::DeployedGraph, brokenSlaGraph},
        {Event::DeployedMtrExport, brokenSlaMtrExport},
        {Event::DeployedPedestrianGraph, brokenSlaPedestrianGraph},
        {Event::DeployedRenderer, brokenSlaRenderer}
    };

    MetricVec brokenSlaTotalExpected {
        baseMetric.event(Event::DeployedTotalBrokenSla).time("2019-01-15 00:00:00")(),
        baseMetric.event(Event::DeployedTotalBrokenSla).time("2019-01-16 00:00:00")()
    };
    cutOffUntrustedBrokenSlaTotal(brokenSlaTotal, brokenSla, settings.deployedEvents);
    UNIT_ASSERT_VALUES_EQUAL(brokenSlaTotal.size(), brokenSlaTotalExpected.size());
    for (size_t i = 0; i < brokenSlaTotalExpected.size(); ++i) {
        UNIT_ASSERT_VALUES_EQUAL_C(
            brokenSlaTotal[i].time.time_since_epoch().count(),
            brokenSlaTotalExpected[i].time.time_since_epoch().count(),
            "(i = "s + std::to_string(i) + ")"s
        );
    }
    for (const auto& [event, metric]: brokenSla) {
        UNIT_ASSERT_VALUES_EQUAL_C(metric.size(), 2, "(event = "s + std::string(toString(event)) + ")"s);
    }
}

} // Y_UNIT_TEST_SUITE

} // namespace maps::wiki::user_edits_metrics::tests
