#pragma once

#include "stat_utils/st_tag.h"

#include <maps/libs/chrono/include/days.h>
#include <maps/libs/chrono/include/time_point.h>
#include <maps/wikimap/mapspro/services/tasks_realtime/src/user_edits_metrics/lib/metrics.h>
#include <maps/wikimap/mapspro/services/tasks_realtime/src/user_edits_metrics/lib/util.h>
#include <maps/wikimap/mapspro/services/tasks_realtime/src/user_edits_metrics/lib/stat_utils/st_agent.h>

namespace maps::wiki::user_edits_metrics {

using namespace chrono::literals;

/**
 * @brief Check metrics and create/update ticket in startrek if necessary
 * @param allMetrics not sorted vector of all metrics
 */
void checkMetricsAndNotifySt(
    const MetricVec& allMetrics,
    const RegionToEvents& regionToDeployedEvents,
    const maps::chrono::TimePoint& calculationDate,
    bool upload);

//! @brief Settings we are interesting in to create ticket in startrek
struct NotifySettings {
    NotifySettings();

    double quantile; //!< percent of the same commits in a day that we consider as good
    bool inTrunk;
    std::string region;
    std::string userType;

    size_t windowSize; //!< the window in days of service level agreement (SLA)
    chrono::Days sla; //!< service level agreement of max time between commit and deployment changes into production
    chrono::Days slaWarningLevel; //!< warning level of total SLA to create notification in startrek
    EventSet deployedEvents;

    std::string queueKey;
    std::string summary;
    std::string referenceToGraphics;
    std::unordered_set<std::string> followers;
};

//! Cut off broken SLA total metrics if they are not based on ALL deployed events
void cutOffUntrustedBrokenSlaTotal(
    MetricVec& brokenSlaTotal,
    std::map<Event, MetricVec>& brokenSla,
    const EventSet& deployedEvents
);

//! @brief Prepare metrics according the settings we are interesting in
class MetricPrepare {
public:
    explicit MetricPrepare(const NotifySettings& settings);

    /**
     * @brief Calculation of total broken SLA metrics
     * @param allMetrics not sorted vector of all metrics
     */
    [[nodiscard]] MetricVec calcBrokenSlaTotalMetrics(
        const MetricVec& allMetrics,
        const maps::chrono::TimePoint& calculationDate) const;

    /**
     * @brief Calculation of broken SLA metrics by deployment event
     * @param allMetrics not sorted vector of all metrics
     */
    [[nodiscard]] std::map<Event, MetricVec> calcBrokenSlaMetrics(
        const MetricVec& allMetrics,
        const maps::chrono::TimePoint& calculationDate) const;

private:
    /**
     * @brief Get total deployed metrics with completion of missed days
     * @param allMetrics not sorted all metrics
     * @return sorted metrics by days
     */
    [[nodiscard]] MetricVec getDeployedTotalMetricsWithEstimations(
        const MetricVec& allMetrics,
        const maps::chrono::TimePoint& calculationDate) const;

    /**
     * @brief Get metrics of days out of SLA
     * @param sortedMetrics sorted metrics of deployed event by days
     * @return sorted metrics by days
     */
    [[nodiscard]] MetricVec getDaysOutOfSlaMetrics(
        MetricVec& sortedMetrics,
        const maps::chrono::TimePoint& calculationDate,
        const Event& event) const;

    const NotifySettings& settings_;
};

/**
 * @brief Startrek notifier
 * Identify incoming problems in broken total SLA.
 * Create/update ticket in startrek if necessary.
 */
class StNotifier
{
public:
    StNotifier(
        const NotifySettings& settings,
        MetricVec& brokenSlaTotal,
        std::map<Event, MetricVec>& brokenSla
    );

    template<typename StAgent>
    void doNotifyLogic(const StAgent& stAgent)
    {
        if (brokenSlaTotal_.back().value < settings_.slaWarningLevel) {
            return;
        }
        auto ticket = stAgent.findOpenedIssue(settings_.summary);
        maps::chrono::TimePoint fromTime;
        if (!ticket) {
            fromTime = brokenSlaTotal_.front().time;
        } else {
            const auto lastCheckedDateInIssue = tagsToDate(ticket->tags());
            fromTime = lastCheckedDateInIssue + 1_days;
        }
        const auto comments = generateComment(fromTime);
        if (!comments.empty()) {
            if (!ticket) {
                ticket = stAgent.createIssue(settings_.summary, getDescription(), settings_.followers);
            }
            stAgent.updateIssue(ticket.value(), comments, brokenSlaTotal_.back().time);
        }
    }
private:
    void eraseMetricsBeforeCrossingWarningLevel();
    [[nodiscard]] std::string generateComment(const maps::chrono::TimePoint& fromTime) const;
    [[nodiscard]] std::string getDescription() const;

    static std::string timeToDate(const maps::chrono::TimePoint& timePoint);

    MetricVec& brokenSlaTotal_; //!< metrics of broken total modules deployment SLA by days
    std::map<Event, MetricVec>& brokenSla_; //!< metrics of broken module deployment SLA by days
    bool dayOfCrossingWarningLevelFound_ = false;
    const NotifySettings& settings_; //!< settings of metrics we are interesting in to create notification
};

} // namespace maps::wiki::user_edits_metrics
