#include "commit_data.h"

#include <maps/libs/log8/include/log8.h>
#include <maps/wikimap/mapspro/libs/views/include/regions.h>

namespace maps::wiki::user_edits_metrics {

EventToTime calcEventToTimeForBranchAndRegion(
    const BranchesById& branchesById,
    revision::DBID branchId,
    const std::string& regionName)
{
    if (!branchId) {
        return {};
    }
    auto branchIt = branchesById.find(branchId);
    REQUIRE(branchIt != branchesById.end(), "branch id: " << branchId << " not found");
    const auto& branchInfo = branchIt->second;

    EventToTime events;
    events.emplace(Event::InStable, branchInfo.created);

    while (branchIt != branchesById.end() && events.size() < COMMIT_EVENTS.size()) {
        const auto& regionToEvents = branchIt->second.regionToEvents;
        auto regionToEventsIt = regionToEvents.find(regionName);
        if (regionToEventsIt != regionToEvents.end()) {
            for (const auto& [event, timePoint] : regionToEventsIt->second) {
                events.try_emplace(event, timePoint);
            }
        }
        ++branchIt;
    }
    return events;
}

EventToTime calcEventToFirstDeployTime(const RegionToEventToTime& regionToEvents)
{
    EventToTime result;
    for (const auto& [_, eventToTime]: regionToEvents) {
        for (const auto& [event, time]: eventToTime) {
            if (result.count(event)) {
                result[event] = std::min(result[event], time);
            } else {
                result.emplace(event, time);
            }
        }
    }
    return result;
}

CommitData::CommitData(
        const revision::Commit& commit,
        const social::Task& task,
        UserType userType)
    : id_(commit.id())
    , userType_(userType)
    , created_(commit.createdAtTimePoint())
    , inTrunk_(commit.inTrunk())
{
    if (task.isResolved()) {
        commonEvents_.emplace(Event::Resolved, chrono::parseSqlDateTime(task.resolved().date()));
    }
    if (task.isClosed()) {
        commonEvents_.emplace(Event::Approved, chrono::parseSqlDateTime(task.closed().date()));
    }
}

// autoapproved commit
CommitData::CommitData(
        const revision::Commit& commit,
        UserType userType)
    : id_(commit.id())
    , userType_(userType)
    , created_(commit.createdAtTimePoint())
    , inTrunk_(commit.inTrunk())
{
    commonEvents_.emplace(Event::Resolved, created_);
    commonEvents_.emplace(Event::Approved, created_);
}

void CommitData::addRegionEvents(
    pqxx::transaction_base& txnViewTrunk,
    revision::DBID stableBranchId,
    const BranchesById& branchesById,
    const CommitIdToRegions& commitIdToRegions,
    const RegionsData& regionsData,
    const geolib3::BoundingBox& bbox)
{
    auto regionsIt = commitIdToRegions.find(id_);
    const auto& regionIds =
        regionsIt != commitIdToRegions.end()
        ? regionsIt->second
        : views::trunk::getRegions(txnViewTrunk, bbox);

    for (const auto regionId: regionIds) {
        auto it = regionsData.find(regionId);
        if (it == regionsData.end()) {
            continue;
        }
        const auto& regionName = it->second;

        regionToEvents_.emplace(
            regionName,
            calcEventToTimeForBranchAndRegion(branchesById, stableBranchId, regionName)
        );
        regionsFromCommit_.emplace(regionName);
    }
}

void CommitData::addAoiRegions(const std::vector<std::string>& aoiNames)
{
    if (regionToEvents_.empty()) {
        WARN() << "Commit " << id_ << " must intersect at least one region";
    }
    EventToTime eventToMinTime = calcEventToFirstDeployTime(regionToEvents_);
    regionToEvents_.emplace(ALL_REGIONS_REGION, eventToMinTime);
    for (const auto& aoiName: aoiNames) {
        regionToEvents_.emplace(aoiName, eventToMinTime);
    }
}

bool CommitData::insideRegion(const std::string& regionName) const
{
    return regionToEvents_.count(regionName) > 0;
}

std::optional<chrono::TimePoint> CommitData::findEvent(
    const std::string& regionName,
    Event event) const
{
    auto eventIt = commonEvents_.find(event);
    if (eventIt != commonEvents_.end()) {
        return eventIt->second;
    }

    auto regionIt = regionToEvents_.find(regionName);
    if (regionIt == regionToEvents_.end()) {
        return std::nullopt;
    }
    eventIt = regionIt->second.find(event);
    if (eventIt == regionIt->second.end()) {
        return std::nullopt;
    }
    return eventIt->second;
}

} // namespace maps::wiki::user_edits_metrics
