#include "branch_events.h"

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

#include <boost/algorithm/string/predicate.hpp>

namespace maps {
namespace wiki {

namespace {

const std::string EVENT_PUBLISHED_REGION_PREFIX = EVENT_DEPLOYED_RENDERER + ":";

std::string extractRegion(const std::string& event)
{
    auto pos = event.find(':');
    return pos != std::string::npos
        ? event.substr(pos + 1)
        : std::string();
}

} // namespace

void EventsHolder::addEvent(const std::string& event, BranchId branchId, chrono::TimePoint time)
{
    INFO() << "Add event " << event << " branch " << branchId << " time " << chrono::formatSqlDateTime(time);

    auto& branchToTime = eventToBranchToTime_[event];

    auto it = branchToTime.emplace(branchId, time).first;
    if (time < it->second) {
        it->second = time;
    }
}

void EventsHolder::addEvent(const std::string& event, const std::string& region, BranchId branchId, chrono::TimePoint time)
{
    addEvent(event + ":" + region, branchId, time);

    if (region == REGION_CIS1) {
        // for backward compatibility
        addEvent(event, branchId, time);
    }
}

// BranchStore

BranchStore::BranchStore(pqxx::transaction_base& coreTxn)
{
    revision::BranchManager branchManager(coreTxn);
    //TODO: add loadAll method to the BranchManager class
    branches_ = branchManager.load({
        {revision::BranchType::Stable, revision::BranchManager::UNLIMITED},
        {revision::BranchType::Archive, revision::BranchManager::UNLIMITED},
        {revision::BranchType::Deleted, revision::BranchManager::UNLIMITED},
    });
    for (const auto& branch : branches_) {
        std::unordered_set<std::string> events;

        for (const auto& pair : branch.attributes()) {
            const auto& key = pair.first;

            for (const auto& baseEvent: BRANCH_BASE_EVENTS) {
                if (boost::algorithm::starts_with(key, baseEvent)) {
                    events.insert(key);
                    break;
                }
            }
        }

        branchToEvents_.emplace(branch.id(), std::move(events));
    }
}

PublishedRegions BranchStore::publishedRegions() const
{
    PublishedRegions result;

    for (const auto& pair : attrsToMerge_) {
        const auto& branchId = pair.first;
        for (const auto& eventToTime : pair.second) {
            const auto& event = eventToTime.first;
            if (!boost::algorithm::starts_with(event, EVENT_PUBLISHED_REGION_PREFIX)) {
                continue;
            }
            auto region = extractRegion(event);
            if (!region.empty()) {
                result[region].insert(branchId);
            }
        }
    }
    return result;
}

void BranchStore::writeAttributes(pqxx::transaction_base& coreTxn)
{
    for (auto& branch: branches_) {
        auto it = attrsToMerge_.find(branch.id());
        if (it != attrsToMerge_.end()) {
            INFO() << "Updated attributes for branch id: " << branch.id();
            for (const auto& pair : it->second) {
                INFO() << "  attribute " << pair.first << "=" << pair.second;
            }
            branch.concatenateAttributes(coreTxn, it->second);
        }
    }
}

void BranchStore::mergeEvents(const EventsHolder& eventToBranchToTime)
{
    for (const auto& pair : eventToBranchToTime.data()) {
        const auto& event = pair.first;
        const auto& branchToTime = pair.second;

        mergeEvent(event, branchToTime);
    }
}

void BranchStore::mergeEvent(
    const std::string& event,
    const BranchToTime& branchToTime)
{
    ASSERT(!branchToTime.empty());

    for (const auto& pair : branchToTime) {
        auto branchId = pair.first;
        auto time = pair.second;

        auto lastBranchToAddEventIt = std::find_if(
            branchToEvents_.rbegin(), branchToEvents_.rend(),
            [&](const auto& pair) {
                return pair.first == branchId;
            });

        if (lastBranchToAddEventIt == branchToEvents_.rend()) {
            //This case is specific for the testing environment
            //when branch ids come from the production Garden.
            WARN() << "Skip merging branch " << branchId << " due to not existence in the DB";
            continue;
        }

        auto lastBranchWithEventIt = std::find_if(
            lastBranchToAddEventIt, branchToEvents_.rend(),
            [&](const auto& pair) {
                return pair.second.count(event);
            });

        auto updateBranch = [&](auto& pair) {
            auto branchId = pair.first;
            auto& events = pair.second;
            auto inserted = events.insert(event).second;
            if (inserted) {
                attrsToMerge_[branchId].emplace(event, chrono::formatSqlDateTime(time));
            }
        };

        if (lastBranchWithEventIt == branchToEvents_.rend()) {
            // This is the first time this event is encountered.
            // Update only the latest entry
            updateBranch(*lastBranchToAddEventIt);
        } else {
            // Update all entries up to the entry with existing event info
            for (auto it = lastBranchToAddEventIt; it != lastBranchWithEventIt; ++it) {
                updateBranch(*it);
            }
        }
    }
}

} // namespace wiki
} // namespace maps
