#include <maps/wikimap/mapspro/services/tasks_realtime/src/user_edits_metrics/lib/util.h>

#include <maps/libs/enum_io/include/enum_io.h>


namespace maps::wiki::user_edits_metrics {

namespace {

std::set<std::string> getAllRegions(
    const EventToRegions& eventToRegions,
    const AoiRegionsData& aoiRegionsData)
{
    std::set<std::string> result;
    for (const auto& [_, regions] : eventToRegions) {
        result.insert(regions.cbegin(), regions.cend());
    }
    for (const auto& [region, _] : aoiRegionsData) {
        result.emplace(region);
    }
    return result;
}

} // namespace

constexpr maps::enum_io::Representations<Event> EVENT_REPRESENTATION {
    {Event::Approved,                         "approved"},
    {Event::Exported,                         "exported"},
    {Event::InStable,                         "in_stable"},
    {Event::Resolved,                         "resolved"},

    {Event::DeployedBicycleGraph,             "deployed_bicycle_graph"},
    {Event::DeployedCams,                     "deployed_cams"},
    {Event::DeployedCarparks,                 "deployed_carparks"},
    {Event::DeployedGeocoder,                 "deployed_geocoder"},
    {Event::DeployedGraph,                    "deployed_graph"},
    {Event::DeployedMtrExport,                "deployed_mtr_export"},
    {Event::DeployedPedestrianGraph,          "deployed_pedestrian_graph"},
    {Event::DeployedRenderer,                 "deployed_renderer"},
    {Event::DeployedTotal,                    "deployed_total"},

    {Event::DeployedBicycleGraphBrokenSla,    "deployed_bicycle_graph_broken_sla"},
    {Event::DeployedCamsBrokenSla,            "deployed_cams_broken_sla"},
    {Event::DeployedCarparksBrokenSla,        "deployed_carparks_broken_sla"},
    {Event::DeployedGeocoderBrokenSla,        "deployed_geocoder_broken_sla"},
    {Event::DeployedGraphBrokenSla,           "deployed_graph_broken_sla"},
    {Event::DeployedMtrExportBrokenSla,       "deployed_mtr_export_broken_sla"},
    {Event::DeployedPedestrianGraphBrokenSla, "deployed_pedestrian_graph_broken_sla"},
    {Event::DeployedRendererBrokenSla,        "deployed_renderer_broken_sla"},
    {Event::DeployedTotalBrokenSla,           "deployed_total_broken_sla"},

    {Event::DeployedBicycleGraphRealSla,      "deployed_bicycle_graph_real_sla"},
    {Event::DeployedCamsRealSla,              "deployed_cams_real_sla"},
    {Event::DeployedCarparksRealSla,          "deployed_carparks_real_sla"},
    {Event::DeployedGeocoderRealSla,          "deployed_geocoder_real_sla"},
    {Event::DeployedGraphRealSla,             "deployed_graph_real_sla"},
    {Event::DeployedMtrExportRealSla,         "deployed_mtr_export_real_sla"},
    {Event::DeployedPedestrianGraphRealSla,   "deployed_pedestrian_graph_real_sla"},
    {Event::DeployedRendererRealSla,          "deployed_renderer_real_sla"},
    {Event::DeployedTotalRealSla,             "deployed_total_real_sla"}
};
DEFINE_ENUM_IO(Event, EVENT_REPRESENTATION);

constexpr maps::enum_io::Representations<Days> DAYS_REPRESENTATION {
    {Days::AllDays,  "alldays"},
    {Days::WorkDays, "workdays"},
};
DEFINE_ENUM_IO(Days, DAYS_REPRESENTATION);

EventToRegions calcEventToRegions(
    const BranchesById& branchesData,
    const chrono::TimePoint& minTime)
{
    EventToRegions result;
    for (const auto& [_, branchInfo] : branchesData) {
        if (branchInfo.created < minTime) {
            continue;
        }
        for (const auto& [region, eventToTime] : branchInfo.regionToEvents) {
            for (const auto& [event, _] : eventToTime) {
                result[event].emplace(region);
            }
        }
    }
    return result;
}

EventToRegions calcEventToRegions(
    const BranchesById& branchesData,
    const AoiRegionsData& aoiRegionsData,
    const chrono::TimePoint& minTime)
{
    EventToRegions result = calcEventToRegions(branchesData, minTime);

    for (auto& [_, regions]: result) {
        regions.emplace(ALL_REGIONS_REGION);
        for (const auto& [region, _] : aoiRegionsData) {
            regions.emplace(region);
        }
    }

    const auto allRegions = getAllRegions(result, aoiRegionsData);

    // Set regions for events that are not related to branches,
    // such as approved, in_stable, resolved
    for (const auto& event: NON_BRANCH_BASE_EVENTS) {
        result[event] = allRegions;
        result[event].emplace(ALL_REGIONS_REGION);
    }

    return result;
}

EventToRegions calcEventToDeployedRegions(
    const BranchesById& branchesData,
    const chrono::TimePoint& minTime)
{
    EventToRegions result = calcEventToRegions(branchesData, minTime);
    const auto allRegions = getAllRegions(result, {}); // not contains ALL_REGIONS_REGION
    for (const auto& event: NON_BRANCH_BASE_EVENTS) {
        result[event] = allRegions;
    }
    return result;
}

RegionToEvents calcRegionToDeployedEvents(const EventToRegions& eventToRegions)
{
    // Calculate all deployed events occured in each region
    RegionToEvents regionToEvents;
    for (const auto& [event, regions] : eventToRegions) {
        if (DEPLOYED_TO_PRODUCTION_EVENTS.count(event) == 0) {
            continue;
        }
        for (const auto& region : regions) {
            regionToEvents[region].emplace(event);
        }
    }
    return regionToEvents;
}

std::string
toString(const std::optional<std::chrono::seconds>& duration)
{
    return duration ? std::to_string(duration->count()) : "";
}

maps::chrono::TimePoint beginningOfTimeInterval(maps::chrono::TimePoint time, std::chrono::seconds interval)
{
    ASSERT(interval.count() != 0);

    maps::chrono::TimePoint::duration timeSinceBeginning{
        time.time_since_epoch().count() % maps::chrono::TimePoint::duration(interval).count()
    };
    return time - timeSinceBeginning;
}

} // namespace maps::wiki::user_edits_metrics
