#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/mpro/include/region.h>

#include <maps/wikimap/mapspro/services/autocart/libs/geometry/include/hex_wkb.h>

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

#include <maps/libs/geolib/include/polygon.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/serialization.h>
#include <maps/libs/geolib/include/test_tools/comparison.h>

#include <pqxx/transaction_base>

#include <string>
#include <vector>
#include <optional>

namespace maps::wiki::autocart::pipeline {

namespace {

static const std::string BLD_RECOGNITION_REGION = "bld_recognition_region";
static const std::string CAT_BLD_RECOGNITION_REGION
    = "cat:" + BLD_RECOGNITION_REGION;
static const std::string BLD_RECOGNITION_REGION_NAME
    = BLD_RECOGNITION_REGION + ":name";
static const std::string BLD_RECOGNITION_REGION_SHAPE
    = BLD_RECOGNITION_REGION + ":shape";
static const std::string BLD_RECOGNITION_REGION_USE_DWELLPLACES
    = BLD_RECOGNITION_REGION + ":use_dwellplaces";
static const std::string BLD_RECOGNITION_REGION_USE_TOLOKERS
    = BLD_RECOGNITION_REGION + ":use_tolokers";
static const std::string BLD_RECOGNITION_REGION_USE_ASSESSORS
    = BLD_RECOGNITION_REGION + ":use_assessors";
static const std::string BLD_RECOGNITION_REGION_IS_ACTIVE
    = BLD_RECOGNITION_REGION + ":is_active";
static const std::string BLD_RECOGNITION_REGION_OBJECT_ID
    = BLD_RECOGNITION_REGION + ":object_id";
static const std::string BLD_RECOGNITION_REGION_COMMIT_ID
    = BLD_RECOGNITION_REGION + ":commit_id";

} // namespace

BldRecognitionRegion::BldRecognitionRegion(const revision::ObjectRevision& object) {
    revisionId_ = object.id();

    const auto& data = object.data();
    REQUIRE(data.geometry && data.attributes,
            "Invalid bld recognition region");
    std::stringstream ss(*data.geometry);
    geoGeom_ = geolib3::convertMercatorToGeodetic(
        geolib3::WKB::read<geolib3::Polygon2>(ss)
    );

    const revision::Attributes& attributes = *data.attributes;

    REQUIRE(attributes.count(BLD_RECOGNITION_REGION_NAME),
            "Region does not have attribute: " + BLD_RECOGNITION_REGION_NAME);
    name_ = attributes.at(BLD_RECOGNITION_REGION_NAME);

    useTolokers_ = attributes.count(BLD_RECOGNITION_REGION_USE_TOLOKERS);
    useAssessors_ = attributes.count(BLD_RECOGNITION_REGION_USE_ASSESSORS);
    useDwellplaces_ = attributes.count(BLD_RECOGNITION_REGION_USE_DWELLPLACES);
    isActive_ = attributes.count(BLD_RECOGNITION_REGION_IS_ACTIVE);
}

revision::RevisionID BldRecognitionRegion::revisionId() const {
    return revisionId_;
}

const std::string& BldRecognitionRegion::name() const {
    return name_;
}

geolib3::Polygon2 BldRecognitionRegion::toMercatorGeom() const {
    return geolib3::convertGeodeticToMercator(geoGeom_);
}

const geolib3::Polygon2& BldRecognitionRegion::toGeodeticGeom() const {
    return geoGeom_;
}

NYT::TNode BldRecognitionRegion::toYTNode() const {
    NYT::TNode node;
    toYTNode(node);
    return node;
}

void BldRecognitionRegion::toYTNode(NYT::TNode& node) const {
    node[BLD_RECOGNITION_REGION_NAME] = TString(name_);
    node[BLD_RECOGNITION_REGION_SHAPE] = TString(polygonToHexWKB(geoGeom_));
    node[BLD_RECOGNITION_REGION_USE_DWELLPLACES] = useDwellplaces_;
    node[BLD_RECOGNITION_REGION_USE_TOLOKERS] = useTolokers_;
    node[BLD_RECOGNITION_REGION_USE_ASSESSORS] = useAssessors_;
    node[BLD_RECOGNITION_REGION_IS_ACTIVE] = isActive_;
    node[BLD_RECOGNITION_REGION_OBJECT_ID] = revisionId_.objectId();
    node[BLD_RECOGNITION_REGION_COMMIT_ID] = revisionId_.commitId();
}

BldRecognitionRegion BldRecognitionRegion::fromYTNode(const NYT::TNode& node) {
    BldRecognitionRegion region;
    region.name_ = node[BLD_RECOGNITION_REGION_NAME].AsString();
    region.geoGeom_ = hexWKBToPolygon(node[BLD_RECOGNITION_REGION_SHAPE].AsString());
    region.useDwellplaces_ = node[BLD_RECOGNITION_REGION_USE_DWELLPLACES].AsBool();
    region.useTolokers_ = node[BLD_RECOGNITION_REGION_USE_TOLOKERS].AsBool();
    region.useAssessors_ = node[BLD_RECOGNITION_REGION_USE_ASSESSORS].AsBool();
    region.isActive_ = node[BLD_RECOGNITION_REGION_IS_ACTIVE].AsBool();
    revision::DBID objectId = node[BLD_RECOGNITION_REGION_OBJECT_ID].AsUint64();
    revision::DBID commitId = node[BLD_RECOGNITION_REGION_COMMIT_ID].AsUint64();
    region.revisionId_ = revision::RevisionID(objectId, commitId);
    return region;
}


bool BldRecognitionRegion::useTolokers() const {
    return useTolokers_;
}

bool BldRecognitionRegion::useAssessors() const {
    return useAssessors_;
}

bool BldRecognitionRegion::useDwellplaces() const {
    return useDwellplaces_;
}

bool BldRecognitionRegion::isActive() const {
    return isActive_;
}

BldRecognitionRegions loadAllBldRecognitionRegions(pqxx::transaction_base& txn) {
    BldRecognitionRegions regions;

    revision::RevisionsGateway gtw(txn);
    revision::Snapshot snapshot = gtw.snapshot(gtw.headCommitId());
    revision::Revisions objects = snapshot.objectRevisionsByFilter(
        revision::filters::Attr(CAT_BLD_RECOGNITION_REGION).defined() &&
        revision::filters::ObjRevAttr::isNotDeleted()
    );

    for (const revision::ObjectRevision& object : objects) {
        regions.emplace_back(object);
    }

    return regions;
}

BldRecognitionRegions
loadAllBldRecognitionRegions(const maps::wiki::common::ExtendedXmlDoc& mproConfig) {
    INFO() << "Connecting to MPRO";
    maps::wiki::common::PoolHolder coreDbHolder(mproConfig, "core", "core");
    pgpool3::TransactionHandle mproTxn = coreDbHolder.pool().slaveTransaction();
    return loadAllBldRecognitionRegions(*mproTxn);
}

std::optional<BldRecognitionRegion>
loadBldRecognitionRegion(pqxx::transaction_base& txn, const std::string& name) {
    revision::RevisionsGateway gtw(txn);
    revision::Snapshot snapshot = gtw.snapshot(gtw.headCommitId());
    revision::Revisions objects = snapshot.objectRevisionsByFilter(
        revision::filters::Attr(CAT_BLD_RECOGNITION_REGION).defined() &&
        revision::filters::Attr(BLD_RECOGNITION_REGION_NAME).equals(name) &&
        revision::filters::ObjRevAttr::isNotDeleted()
    );

    REQUIRE(objects.size() <= 1, "There is more than one region with name: " + name);
    if (!objects.empty()) {
        return BldRecognitionRegion(objects.front());
    } else {
        return std::nullopt;
    }
}

bool BldRecognitionRegion::operator==(const BldRecognitionRegion& that) const {
    return revisionId_ == that.revisionId_
        && name_ == that.name_
        && useTolokers_ == that.useTolokers_
        && useAssessors_ == that.useAssessors_
        && useDwellplaces_ == that.useDwellplaces_
        && isActive_ == that.isActive_
        && geolib3::test_tools::approximateEqual(geoGeom_, that.geoGeom_, geolib3::EPS);
}

void removeInactiveRegions(BldRecognitionRegions& regions) {
    regions.erase(
        std::remove_if(
            regions.begin(), regions.end(),
            [](const BldRecognitionRegion& region) {
                if (region.isActive()) {
                    INFO() << "Region " << region.name() << " is active";
                    return false;
                } else {
                    INFO() << "Region " << region.name() << " is inactive";
                    return true;
                }
            }
        ),
        regions.end()
    );
}

void removeUsedRegions(
    BldRecognitionRegions& regions,
    const std::set<std::string>& processingRegionNames)
{
    if (processingRegionNames.empty()) {
        return;
    }

    regions.erase(
        std::remove_if(regions.begin(), regions.end(),
            [&](const BldRecognitionRegion& region) {
                if (processingRegionNames.count(region.name()) > 0) {
                    INFO() << region.name() << " region is used in Hitman now";
                    return true;
                }
                return false;
            }
        ),
        regions.end()
    );
}

} // namespace maps::wiki::autocart::pipeline
