#include <maps/libs/log8/include/log8.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/cmdline/include/cmdline.h>

#include <maps/wikimap/mapspro/services/autocart/libs/utils/include/pool.h>

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

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

#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/objects/include/area.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/objects/include/road.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/objects/include/building.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/objects/include/dwellplace.h>

#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/ymapsdf.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/yt_utils/include/rows_count.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/yt_utils/include/op_wrapper.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/state.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/filter_by_regions.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/filter_by_ft_type_id.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/detect_blds_in_cells.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/inference_auto_toloker.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/create_cells_with_objects.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/filter_dwellplaces_by_blds.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/extract_unique_dwellplaces.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/extract_unique_rows.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/cover_by_cells.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/remove_rejected.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/detection/include/execution.h>

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

#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/factory/include/release.h>

#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/hitman_client/include/client.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/hitman_processes/include/processes.h>

#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/yt_storage.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/detection_results.h>

#include <maps/wikimap/mapspro/services/mrc/libs/yt/include/common.h>

using namespace maps;
using namespace maps::wiki::autocart;
using namespace maps::wiki::autocart::pipeline;
using namespace maps::wiki::autocart::pipeline::hitman;

namespace {

geolib3::MultiPolygon2 mergeReleasesGeometries(
    const std::map<Release, ReleaseAttrs>& releaseToAttrs)
{
    std::vector<geolib3::MultiPolygon2> multiPolygons;
    for (const auto& [release, attrs] : releaseToAttrs) {
        for (const ReleaseGeometry& geometry : attrs.geometries) {
            multiPolygons.push_back(geometry.mercatorGeom);
        }
    }
    return mergeMultiPolygons(multiPolygons);
}

std::unordered_map<std::string, geolib3::MultiPolygon2>
intersectsRegionsWithNewReleases(
    FactoryServiceClient& factoryClient,
    BldRecognitionRegions& regions,
    const std::unordered_map<TString, ProcessedIssue>& regionToIssue,
    const std::map<Release, ReleaseAttrs>& releaseToAttrs)
{
    std::unordered_map<std::string, geolib3::MultiPolygon2> regionToMercGeom;

    for (const BldRecognitionRegion& region : regions) {
        auto it = regionToIssue.find(TString(region.name()));
        if (it != regionToIssue.end()) {
            std::optional<Release> release = getReleaseByIssueId(factoryClient, it->second.issueId);
            REQUIRE(release.has_value(),
                    "Failed to get release with issue id " << it->second.issueId);
            auto firstNewReleaseIt = releaseToAttrs.upper_bound(release.value());
            REQUIRE(firstNewReleaseIt != releaseToAttrs.end(),
                    "Region is up to date");
            geolib3::MultiPolygon2 newReleasesMercGeom
                = mergeReleasesGeometries({firstNewReleaseIt, releaseToAttrs.end()});
            geolib3::MultiPolygon2 intersectedMercGeom = intersectMultiPolygons(
                newReleasesMercGeom, geolib3::MultiPolygon2({region.toMercatorGeom()})
            );
            if (intersectedMercGeom.polygonsNumber() > 0) {
                INFO() << region.name() << " region instersects with new releases";
                regionToMercGeom[region.name()] = intersectedMercGeom;
            }
        } else {
            INFO() << region.name() << " region is new";
            regionToMercGeom[region.name()]
                = geolib3::MultiPolygon2({region.toMercatorGeom()});
        }
    }
    return regionToMercGeom;
}

void intersectsRegionsWithCoverageAtZoom(
    std::unordered_map<std::string, geolib3::MultiPolygon2>& regionToMercGeom,
    const geolib3::MultiPolygon2& coverageAtZoom)
{
    for (auto it = regionToMercGeom.begin(); it != regionToMercGeom.end();) {
        geolib3::MultiPolygon2 intersectedMercGeom = intersectMultiPolygons(
            coverageAtZoom, it->second
        );
        if (intersectedMercGeom.polygonsNumber() > 0) {
            it->second = intersectedMercGeom;
            it++;
        } else {
            it = regionToMercGeom.erase(it);
        }
    }
}

/*
Имеем два набора релизов:
1) Готовые релизы, которые мы уже видели 3 недели назад (newReleaseToAttrs)
2) Новый релизы, на которых еще нельзя детектировать (notReadyReleaseToAttrs)

Нужно найти покрытие новыми релизами (newReleaseToAttrs)
на нужном зуме (detectorConfig.zoom()). Так как на не готовых релизах
детектировать нельзя, то необходимо удалить из полученного покрытия регионы,
которые покрываются еще не готовыми релизами (notReadyReleaseToAttrs).

Для этого вызываем функцию removeNotReadyReleases, которая принимает три аргумента:
1) regionToMercGeom - отображение имени региона в покрытие,
   полученное новыми релизами (newReleaseToAttrs)
2) notReadyRegionToAttrs - набор неготовых атрибутов
3) zoom - зум, на котором нужно удалить неготовые покрытия
   (тот же зум, на котором производится детектирование)

Что делает функция removeNotReadyReleases?
Собираем все мультиполигоны в координатах меркатора из неготовых релизов,
которые существуют на указанном зуме. Объединяем их и получаем итоговый
мультиполигон (notReadyMercGeom). Далее проходим по все регионам и
вычитаем из их покрытия найденным мультиполигон. Если результат вычитания пустой,
то запись о регионе удаляется, иначе геометрия покрытия переписывается.

Если у региона вышло пустой покрытия (т.е. записи о нем нет в regionToMercGeom),
то у него надо обновить запись о последнем обработанном
релизе через updateAndRemoveNotIntersectedRegions.
*/
void removeNotReadyReleases(
    std::unordered_map<std::string, geolib3::MultiPolygon2>& regionToMercGeom,
    const ReleaseToAttrs& notReadyReleaseToAttrs,
    int zoom)
{
    std::vector<geolib3::MultiPolygon2> notReadyMercGeoms;
    for (const auto& [release, attrs] : notReadyReleaseToAttrs) {
        for (const auto& geometry : attrs.geometries) {
            if (geometry.zmin <= zoom && zoom <= geometry.zmax) {
                notReadyMercGeoms.push_back(geometry.mercatorGeom);
            }
        }
    }
    geolib3::MultiPolygon2 notReadyMercGeom = mergeMultiPolygons(notReadyMercGeoms);

    for (auto it = regionToMercGeom.begin(); it != regionToMercGeom.end();) {
        geolib3::MultiPolygon2 differenceMercGeom = differenceMultiPolygons(
            it->second, notReadyMercGeom
        );
        if (differenceMercGeom.polygonsNumber() > 0) {
            it->second = differenceMercGeom;
            it++;
        } else {
            it = regionToMercGeom.erase(it);
        }
    }
}

void updateAndRemoveNotIntersectedRegions(
    BldRecognitionRegions& regions,
    const std::unordered_map<std::string, geolib3::MultiPolygon2>& regionToMercGeom,
    const Release& lastRelease,
    YTStorageClient& storage)
{
    for (int i = static_cast<int>(regions.size()) - 1; i >= 0; i--) {
        auto it = regionToMercGeom.find(regions[i].name());
        if (it == regionToMercGeom.end()) {
            INFO() << "Updating issue id for region " << regions[i].name();
            storage.updateIssue(TString(regions[i].name()), lastRelease.issueId);
            INFO() << "Removing " << regions[i].name() << " region";
            regions.erase(regions.begin() + i);
        }
    }
}

geolib3::MultiPolygon2 mergeRegionsGeometries(const BldRecognitionRegions& regions) {
    std::vector<geolib3::Polygon2> mercRegions;
    for (const BldRecognitionRegion& region : regions) {
        mercRegions.push_back(region.toMercatorGeom());
    }
    return mergePolygons(mercRegions);
}

mrc::yt::PoolType parseYTPoolType(const std::string& value) {
    if (value == "ad_hoc") {
        return mrc::yt::PoolType::AdHoc;
    } else if (value == "processing") {
        return mrc::yt::PoolType::Processing;
    } else {
       throw maps::RuntimeError() << "Unsupported ytPoolType value '" << value << "'";
    }
}

} // namespace

int main(int argc, const char** argv)
try {
    NYT::Initialize(argc, argv);

    maps::cmdline::Parser parser("Detect building in cells");

    maps::cmdline::Option<std::string> ytConfigPath = parser.string("yt_config")
        .required()
        .help("Path to YT config");

    maps::cmdline::Option<std::string> detectorConfigPath = parser.string("detector_config")
        .required()
        .help("Path to detector config file");

    maps::cmdline::Option<std::string> autoTolokerConfigPath = parser.string("auto_toloker_config")
        .required()
        .help("Path to toloker config file");

    maps::cmdline::Option<std::string> mproConfigPath = parser.string("mpro_config")
        .required()
        .help("Path to mpro config file");

    maps::cmdline::Option<std::string> hitmanToken = parser.string("hitman_token")
        .required()
        .help("Token for Hitman client");

    maps::cmdline::Option<std::string> ytPoolType = parser.string("yt_pool_type")
            .defaultValue("processing")
            .help("yt pool type ('ad_hoc' or 'processing')");

    maps::cmdline::Option<bool> useGPU = parser.flag("use_gpu")
        .defaultValue(false)
        .help("Enable GPU on YT");

    parser.parse(argc, const_cast<char**>(argv));


    YTOpExecutor::Initialize(parseYTPoolType(ytPoolType), useGPU);

    INFO() << "Loading YT config: " << ytConfigPath;
    YTConfig ytConfig(ytConfigPath);

    INFO() << "Loading detector config: " << detectorConfigPath;
    DetectorConfig detectorConfig(detectorConfigPath);

    INFO() << "Loading auto toloker config: " << autoTolokerConfigPath;
    AutoTolokerConfig autoTolokerConfig(autoTolokerConfigPath);

    INFO() << "Loading mpro config: " << mproConfigPath;
    maps::wiki::common::ExtendedXmlDoc mproConfig(mproConfigPath);


    INFO() << "Create YT client: yt::hahn";
    NYT::IClientPtr client = NYT::CreateClient("hahn");

    INFO() << "Create YT storage client: " << ytConfig.storagePath();
    YTStorageClient storage(client, ytConfig.storagePath());

    INFO() << "Create Hitman client: " << PRODUCTION_HITMAN_HOST;
    HitmanClient hitmanClient(PRODUCTION_HITMAN_HOST, hitmanToken);

    ProtoFactoryServiceClient factoryServiceClient;

    INFO() << "Loading building recognition regions from MPRO";
    BldRecognitionRegions regions = loadAllBldRecognitionRegions(mproConfig);
    if (!regions.empty()) {
        INFO() << "Loaded " << regions.size() << " regions";
    } else {
        INFO() << "There is no building recognition regions. Exit";
        return EXIT_SUCCESS;
    }

    INFO() << "Removing inactive regions";
    removeInactiveRegions(regions);
    if (!regions.empty()) {
        INFO() << regions.size() << " regions left";
    } else {
        INFO() << "All regions are inactive. Exit";
        return EXIT_SUCCESS;
    }


    INFO() << "Loading attrs for all releases";

    INFO() << "Loading attrs from YT storage";
    ReleaseToAttrs releaseToAttrs = storage.getAllReleasesAttrs();
    INFO() << "Loaded attrs for "
           << releaseToAttrs.size() << " releases from YT storage";
    INFO() << "Last release in YT storage: " << releaseToAttrs.crbegin()->first;

    INFO() << "Loading geometries from factory";
    std::map<Release, ReleaseGeometries> newReleaseToGeometries;
    if (releaseToAttrs.empty()) {
        newReleaseToGeometries = loadAllReleasesGeometries(factoryServiceClient);
    } else {
        newReleaseToGeometries = loadAllNextReleasesGeometries(
            factoryServiceClient,
            releaseToAttrs.crbegin()->first.releaseId
        );
    }
    INFO() << "Loaded geometries for "
           << newReleaseToGeometries.size() << " releases from factory";

    if (!newReleaseToGeometries.empty()) {
        INFO() << "Upload new " << newReleaseToGeometries.size()
               << " releases to YT storage: " << ytConfig.storagePath();
        chrono::TimePoint now = chrono::TimePoint::clock::now();
        for (const auto& [release, geometries] : newReleaseToGeometries) {
            ReleaseAttrs attrs{geometries, now};
            storage.addRelease(release, attrs);
            INFO() << "Release " << release << " has been uploaded to YT storage";
            releaseToAttrs.emplace(release, attrs);
        }
    }

    INFO() << "Split not ready (newest) releases";
    geolib3::MultiPolygon2 coverageAtZoom = storage.updateCoverage(releaseToAttrs, detectorConfig.zoom());
    auto [newReleaseToAttrs, notReadyReleaseToAttrs] = splitNotReadyReleases(releaseToAttrs);
    INFO() << "There are " << newReleaseToAttrs.size() << " new releases";
    INFO() << "There are " << notReadyReleaseToAttrs.size() << " not ready releases";

    if (newReleaseToAttrs.empty()) {
        return EXIT_SUCCESS;
    }


    INFO() << "Loading processed issues from YT storage";
    std::unordered_map<TString, ProcessedIssue> regionToIssue = storage.getRegionToIssueMap();
    INFO() << "Loaded issues for " << regionToIssue.size() << " regions";

    INFO() << "Loading last satellite factory release";
    Release lastRelease = newReleaseToAttrs.crbegin()->first;
    INFO() << "Last release: " << lastRelease;


    INFO() << "Removing up to date regions";
    chrono::TimePoint dumpDate = YMapsDFYTTable::getDate(client);
    removeUptodateRegions(regions, lastRelease, regionToIssue, dumpDate);
    if (!regions.empty()) {
        INFO() << regions.size() << " regions left";
    } else {
        INFO() << "All region are up to date. Exit";
        return EXIT_SUCCESS;
    }


    INFO() << "Removing regions that used in Hitman";

    INFO() << "Loading names of regions from Hitman jobs";
    auto usedRegionNames = loadRegionsInProcessing(hitmanClient);
    INFO() << "Loaded " << usedRegionNames.size() << " names";
    removeUsedRegions(regions, usedRegionNames);

    if (!regions.empty()) {
        INFO() << regions.size() << " regions left";
    } else {
        INFO() << "All regions are used in Hitman. Exit";
        return EXIT_SUCCESS;
    }
    if (hasFailedJobs(hitmanClient)) {
        INFO() << "There is error in Hitman jobs. Exit";
        return EXIT_SUCCESS;
    }

    INFO() << "Instersects regions with new releases";
    std::unordered_map<std::string, geolib3::MultiPolygon2> regionToMercGeom
        = intersectsRegionsWithNewReleases(factoryServiceClient, regions, regionToIssue, newReleaseToAttrs);

    INFO() << "Removing regions that do not intersects with new releases";
    updateAndRemoveNotIntersectedRegions(regions, regionToMercGeom, lastRelease, storage);
    if (!regions.empty()) {
        INFO() << regions.size() << " regions left";
    } else {
        INFO() << "All regions do not intersects with new releases. Exit";
        return EXIT_SUCCESS;
    }


    INFO() << "Intersects regions with coverage at zoom " << detectorConfig.zoom();
    intersectsRegionsWithCoverageAtZoom(regionToMercGeom, coverageAtZoom);

    INFO() << "Removing regions that do not intersects with"
           << " coverage at zoom " << detectorConfig.zoom();
    updateAndRemoveNotIntersectedRegions(regions, regionToMercGeom, lastRelease, storage);
    if (!regions.empty()) {
        INFO() << regions.size() << " regions left";
    } else {
        INFO() << "All regions do not intersects with"
               << " coverage at zoom " << detectorConfig.zoom();
        return EXIT_SUCCESS;
    }


    INFO() << "Removing not ready releases at zoom " << detectorConfig.zoom();
    removeNotReadyReleases(
        regionToMercGeom,
        notReadyReleaseToAttrs, detectorConfig.zoom()
    );

    INFO() << "Removing regions that totally covered by not ready releases"
           << " at zoom " << detectorConfig.zoom();
    updateAndRemoveNotIntersectedRegions(regions, regionToMercGeom, lastRelease, storage);
    if (!regions.empty()) {
        INFO() << regions.size() << " regions left";
    } else {
        INFO() << "All regions are totally covered by not ready releases";
        return EXIT_SUCCESS;
    }


    INFO() << "Start detection for " << regions.size() << " regions";

    INFO() << "Initialize detection state: " << ytConfig.statePath();
    State::initialize(client, ytConfig.statePath());

    INFO() << "Filtering buildings and roads by regions";
    geolib3::MultiPolygon2 mercRegions = mergeRegionsGeometries(regions);

    ThreadPool mtpQueue(3);
    mtpQueue.Add([&]{
        filterByRegions<Building>(
            client,
            YMapsDFYTTable::getBuildingsYTTablePaths(client),
            mercRegions,
            State::buildingsTablePath()
        );
    });
    mtpQueue.Add([&]{
        filterByRegions<Road>(
            client,
            YMapsDFYTTable::getRoadsYTTablePaths(client),
            mercRegions,
            State::roadsTablePath()
        );
    });
    mtpQueue.Add([&]{
        filterAreasByFTTypeId(
            client,
            YMapsDFYTTable::getFTYTTablePaths(client),
            YMapsDFYTTable::getFTGeomYTTablePaths(client),
            {FTTypeId::URBAN_INDUSTRIAL, FTTypeId::URBAN_UNDER_CONSTRUCTION},
            State::areasTablePath()
        );
        filterByRegions<Area>(
            client,
            {State::areasTablePath()},
            mercRegions,
            State::areasTablePath()
        );
    });
    mtpQueue.Wait();

    INFO() << "Filtered " << getRowsCount(client, State::roadsTablePath()) << " roads";
    INFO() << "Filtered " << getRowsCount(client, State::buildingsTablePath()) << " buildings";
    INFO() << "Filtered " << getRowsCount(client, State::areasTablePath()) << " areas";

    INFO() << "Create cells with objects";

    INFO() << "Creating cells using only regions";
    NYT::TTempTable cellsByRegionsTable = State::getTempTable(client);
    std::vector<geolib3::MultiPolygon2> mercGeomsForCellsByRegions;
    for (const BldRecognitionRegion& region : regions) {
        if (!region.useDwellplaces()) {
            mercGeomsForCellsByRegions.push_back(
                regionToMercGeom.at(region.name())
            );
        }
    }
    if (!mercGeomsForCellsByRegions.empty()) {
        INFO() << mercGeomsForCellsByRegions.size() << " regions do not use dwellplaces";
        createCellsWithObjectsByRegions(
            client,
            State::buildingsTablePath(),
            State::roadsTablePath(),
            State::areasTablePath(),
            mergeMultiPolygons(mercGeomsForCellsByRegions),
            detectorConfig.cellSizeMercator(),
            detectorConfig.padSizeMercator(),
            cellsByRegionsTable.Name()
        );
        INFO() << "Created " << getRowsCount(client, cellsByRegionsTable.Name()) <<  " cells";
    } else {
        INFO() << "There is no regions that not use dwellplaces";
    }

    INFO() << "Creating cells using dwellplaces";
    NYT::TTempTable cellsByDwellplacesTable = State::getTempTable(client);
    std::vector<geolib3::MultiPolygon2> mercGeomsForCellsByDwellplaces;
    for (const BldRecognitionRegion& region : regions) {
        if (region.useDwellplaces()) {
            mercGeomsForCellsByDwellplaces.push_back(
                regionToMercGeom.at(region.name())
            );
        }
    }
    if (!mercGeomsForCellsByDwellplaces.empty()) {
        INFO() << mercGeomsForCellsByDwellplaces.size() << " regions use dwellplaces";
        INFO() << "Extract unique dwellplaces";
        extractUniqueDwellplaces(
            client,
            detectorConfig.dwellplacesYTFolder(),
            State::dwellplacesTablePath(),
            detectorConfig.dwellplaceDistanceMeters()
        );
        INFO() << "Filtering dwellplaces in regions";
        filterByRegions<Dwellplace>(
            client,
            {State::dwellplacesTablePath()},
            mergeMultiPolygons(mercGeomsForCellsByDwellplaces),
            State::dwellplacesTablePath()
        );
        INFO() << "Filtering dwellplaces that close to buildings";
        filterDwellplacesByBuildings(
            client,
            State::dwellplacesTablePath(),
            State::buildingsTablePath(),
            detectorConfig.dwellplaceDistanceMeters(),
            State::dwellplacesTablePath()
        );
        INFO () << "Creating cells using dwellplaces";
        createCellsWithObjectsByDwellplaces(
            client,
            State::buildingsTablePath(),
            State::roadsTablePath(),
            State::areasTablePath(),
            State::dwellplacesTablePath(),
            detectorConfig.cellSizeMercator(),
            detectorConfig.padSizeMercator(),
            cellsByDwellplacesTable.Name()
        );
        INFO() << "Created " << getRowsCount(client, cellsByDwellplacesTable.Name()) <<  " cells";
    } else {
        INFO() << "There is no regions that use dwellplaces";
    }


    INFO() << "Extracting unique cells";
    extractUniqueRows(
        client,
        {cellsByRegionsTable.Name(), cellsByDwellplacesTable.Name()},
        State::cellsTablePath(),
        {Cell::X, Cell::Y}
    );
    INFO() << "Extracted " << getRowsCount(client, State::cellsTablePath()) <<  " unique cells";


    INFO() << "Detecting buildings in cells";
    detectBuildingsInCells(
        client,
        State::cellsTablePath(),
        detectorConfig,
        State::detectionTablePath()
    );
    INFO() << "Detection table: " << State::detectionTablePath();
    INFO() << "Detected " << getRowsCount(client, State::detectionTablePath()) << " buildings";


    INFO() << "Inference auto toloker for detection results";
    inferenceAutoToloker(
        client,
        State::detectionTablePath(),
        autoTolokerConfig,
        State::validationTablePath(),
        EraseBatch::No
    );
    INFO() << "Toloker table: " << State::validationTablePath();

    INFO() << "Removing buildings that intersects with rejected buildings";
    removeRejectedBuildings(
        client,
        State::validationTablePath(),
        detectorConfig,
        storage,
        State::notRejectedTablePath()
    );
    INFO() << getRowsCount(client, State::notRejectedTablePath()) << " buildings left";


    INFO() << "Saving detection results";
    for (const BldRecognitionRegion& region : regions) {
        NYT::TTempTable resultTable = State::getTempTable(client);
        extractByRegions<Building>(
            client,
            State::notRejectedTablePath(),
            regionToMercGeom.at(region.name()),
            resultTable.Name()
        );
        size_t existingResultsCount = storage.getResultsCount<DetectionResult>();
        std::vector<DetectionResult> results;
        NYT::TTableReaderPtr<NYT::TNode> reader
            = client->CreateTableReader<NYT::TNode>(resultTable.Name());
        size_t acceptedResultsCount = 0;
        for (size_t i = 0; reader->IsValid(); reader->Next(), i++) {
            const NYT::TNode& row = reader->GetRow();
            DetectionResult result = DetectionResult::fromYTNode(row);
            result.id = existingResultsCount + i;
            results.push_back(result);
            if (TolokaState::Yes == result.state) {
                acceptedResultsCount++;
            }
        }
        if (!results.empty()) {
            INFO() << "Found " << results.size() << " buildings in region " << region.name();
            INFO() << "Saving results into YT storage";
            storage.saveResults(TString(region.name()), lastRelease.issueId, results);
        }
        if (acceptedResultsCount > 0) {
            INFO() << "Found " << acceptedResultsCount << " accepted buildings";
            INFO() << "Running Hitman job for create ST issue";
            HitmanJobId jobId = runSTIssueProcess(
                hitmanClient,
                region.revisionId().objectId(), region.name(),
                region.useTolokers(), region.useAssessors(),
                lastRelease.issueId
            );
            INFO() << "Job " << jobId << " was run";
        } else {
            INFO() << "Not found accepted buildings in region " << region.name();
            INFO() << "Updating issue id to " << lastRelease.issueId
                   << " for region " << region.name();
            storage.updateIssue(TString(region.name()), lastRelease.issueId);
        }
    }

    INFO() << "Removing detection state: " << ytConfig.statePath();
    State::remove(client);

    return EXIT_SUCCESS;
}
catch (const maps::Exception& e) {
    INFO() << e;
    return EXIT_FAILURE;
}
catch (const std::exception& e) {
    INFO() << e.what();
    return EXIT_FAILURE;
}
catch (...) {
    INFO() << "Caught unknown exception";
    return EXIT_FAILURE;
}
