#include "common.h"
#include "fixture.h"
#include "mocks.h"

#include <library/cpp/testing/unittest/env.h>
#include <library/cpp/testing/unittest/registar.h>
#include <library/cpp/testing/gmock_in_unittest/gmock.h>

#include <maps/infra/yacare/include/test_utils.h>
#include <maps/infra/yacare/include/yacare.h>
#include <maps/libs/chrono/include/days.h>
#include <maps/libs/common/include/file_utils.h>
#include <maps/libs/geolib/include/spatial_relation.h>
#include <maps/libs/img/include/algorithm.h>
#include <maps/libs/img/include/raster.h>
#include <maps/libs/json/include/prettify.h>
#include <maps/libs/tile/include/geometry.h>
#include <maps/libs/tile/include/tile.h>
#include <maps/renderer/libs/data_sets/data_set_test_util/schema_util.h>
#include <maps/wikimap/mapspro/services/mrc/libs/fb/include/coverage_rtree_writer.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/export_gen/lib/graph.h>
#include <yandex/maps/mrc/unittest/utils.h>
#include <yandex/maps/proto/renderer/vmap2/tile.pb.h>
#include <yandex/maps/proto/renderer/vmap3/tile.pb.h>

#include <boost/lexical_cast.hpp>
#include <boost/range/algorithm_ext/erase.hpp>

#include <sstream>
#include <tuple>

namespace maps::mrc::browser::tests {

using TileV2 = yandex::maps::proto::renderer::vmap2::Tile;
using TileV3 = yandex::maps::proto::renderer::vmap3::Tile;

namespace {

using TestRequest = std::tuple<std::string, int, int, int>;

constexpr size_t MAX_IMAGE_ERROR = 16;
constexpr float MEAN_IMAGE_ERROR = 0.2f;

json::Value loadExpectedHotspot(const std::string& layer, int x, int y, int z)
{
    std::stringstream fileName;
    fileName << "expected/hotspot_" << layer << "_" << x << "_" << y << "_" << z << ".json";
    return json::Value::fromFile(SRC_(fileName.str()));
}


img::RasterImage loadExpectedTile(const std::string& layer, int x, int y, int z)
{
    std::stringstream fileName;
    fileName << "expected/tiles_" << layer << "_" << x << "_" << y << "_" << z << ".png";
    return img::RasterImage::fromPngFile(SRC_(fileName.str()));
}
/* todo:
std::string loadExpectedTileTxt(const std::string& layer, int x, int y, int z,
                                std::optional<int> zmin = std::nullopt,
                                std::optional<int> zmax = std::nullopt)
{
    std::stringstream fileName;
    fileName << "expected/tiles_" << layer << "_" << x << "_" << y << "_" << z;
    if (zmin && zmax) {
        fileName << "_" << *zmin << "_" << *zmax;
    }
    fileName  << ".txt";
    return maps::common::readFileToString(SRC_(fileName.str()));
}
*/

const Graph& graph()
{
    static auto result = Graph(TEST_GRAPH_PATH, EMappingMode::Standard);
    return result;
}

road_graph::EdgeId edgeInWindow(const Graph& graph, const geolib3::BoundingBox& mercatorBox)
{
    road_graph::EdgeId result{};
    auto edgeIds = graph.rtree().baseEdgesInWindow(
        convertMercatorToGeodetic(mercatorBox));
    auto minFc = std::numeric_limits<db::TFc>::max();
    for (const auto& edgeId : edgeIds) {
        auto edgeData = graph.graph().edgeData(edgeId);
        auto fc = db::TFc(edgeData.category());
        if (fc < minFc) {
            result = edgeId;
            minFc = fc;
        }
    }
    return result;
}

void fill(pgpool3::Pool& pool, const geolib3::BoundingBox& mercatorBox, db::FeaturePrivacy privacy)
{
    auto edgeId = edgeInWindow(graph(), mercatorBox);
    auto edgeData = graph().graph().edgeData(edgeId);
    auto line = geolib3::Polyline2(edgeData.geometry());

    auto txn = pool.masterWriteableTransaction();

    EXPECT_FALSE(db::FeatureGateway{*txn}.exists(
        db::table::Feature::pos.intersects(mercatorBox)));
    auto time = chrono::TimePoint::clock::now() - chrono::Days{10};
    auto meters = 0.;
    db::Features features;
    for (const auto& segment : line.segments()) {
        geolib3::Direction2 dir{convertGeodeticToMercator(segment)};
        time += int(meters / 10.) * std::chrono::seconds{1};
        meters += geoLength(segment);
        features
            .emplace_back(std::string{"sourceId"},
                          segment.start(),
                          dir.heading(),
                          chrono::formatSqlDateTime(time),
                          mds::Key{"groupId", "path"},
                          db::Dataset::Agents)
            .setSize(6, 9)
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true)
            .setPrivacy(privacy);
    }
    db::FeatureGateway{*txn}.insert(features);
    db::updateFeaturesTransaction(features, *txn);
    txn->commit();

    auto ctx = export_gen::Context(TEST_GRAPH_PATH, db::GraphType::Road);
    auto photos = export_gen::Photos{};
    for (const auto& feature : features) {
        photos.push_back(export_gen::toPhoto(feature));
    }
    auto [graphCoverage, _] = export_gen::makeGraphSummary(
        ctx, photos, export_gen::makeTrackPointProvider(pool));
    boost::remove_erase_if(graphCoverage.edges, [=](const auto& edge) {
        return road_graph::EdgeId(edge.id) != edgeId;
    });
    EXPECT_EQ(graphCoverage.edges.size(), 1u);
    for (auto& coverage : graphCoverage.edges.at(0).coverages) {
        coverage.coverageFraction = 0.5;
    }
    fb::writeToFile(graphCoverage, TEST_GRAPH_PATH + "/graph_coverage.fb");
    fb::writeCoverageRtreeToDir(ctx.matcher.graph(), graphCoverage, TEST_GRAPH_PATH);
}

chrono::TimePoint featureTime(json::Value feature)
{
    auto str = feature["properties"]["properties"]["timestamp"].toString();
    return chrono::parseIsoDateTime(str);
}

}

Y_UNIT_TEST_SUITE(render_api_should) {

Y_UNIT_TEST(test_tiles_requests)
{
    FixtureFb fixture;

    std::vector<TestRequest> testRequests = {
        {"mrcss", 326229, 162882, 19},
        {"mrcss", 326222, 162883, 19},
        {"mrcss", 79222, 41104, 17},
        {"mrcss", 19805,10276, 15},
        {"mrcss", 9902,5138, 14},
        {"mrce", 79222, 41104, 17},
        {"mrce", 19805,10276, 15},
        {"mrce", 9902,5138, 14},
        {"mrcpe", 79222, 41104, 17},
        {"mrcpe", 19805,10276, 15},
        {"mrcpe", 9902,5138, 14},
        {"mrcpent", 79222, 41104, 17},
        {"mrcpent", 19805,10276, 15},
        {"mrcpent", 9902,5138, 14},
    };

    for (const auto& [layer, x, y, z] : testRequests)
    {
        http::MockRequest rq(http::GET,
            http::URL("http://localhost/tiles")
                .addParam("x", x)
                .addParam("y", y)
                .addParam("z", z)
                .addParam("l", layer)
        );
        rq.headers.emplace(yacare::tests::USER_ID_HEADER, std::to_string(fixture.UID));
        auto resp = yacare::performTestRequest(rq);

        EXPECT_EQ(resp.status, 200);

        std::stringstream fileName;
        fileName << "tiles_" << layer << "_" << x << "_" << y << "_" << z << ".png";
        ::maps::common::writeFile(fileName.str(), resp.body);

        EXPECT_LT(measureMaxAbsoluteError(
                      img::RasterImage::fromPngBlob(common::toBytes(resp.body)),
                      loadExpectedTile(layer, x, y, z)),
                  MAX_IMAGE_ERROR
        ) << "image differs for tile " << layer << " " << x << ", " << y << ", " << z;
    }
}

Y_UNIT_TEST(test_hotspot_requests)
{
    FixtureFb fixture;
    const std::string SCHEMA_PATH =
        apiResponseSchemaPath("browser/get_hotspots.response.schema.json");

    std::vector<TestRequest> testRequests = {
        {"mrcss", 79222, 41104, 17},
        {"mrcss", 19805, 10276, 15},
        {"mrcss", 9902, 5138, 14},
        {"mrcss", 1238, 624, 11},
        {"mrce", 79222, 41104, 17},
        {"mrce", 19805, 10276, 15},
        {"mrce", 9902, 5138, 14},
        {"mrcpe", 79222, 41104, 17},
        {"mrcpe", 9902, 5138, 14},
        {"mrcpent", 79222, 41104, 17},
        {"mrcpent", 9902, 5138, 14},
    };

    for (const auto& [layer, x, y, z] : testRequests)
    {
        http::MockRequest rq(http::GET,
            http::URL("http://localhost/features/hotspot")
                .addParam("x", x)
                .addParam("y", y)
                .addParam("z", z)
                .addParam("l", layer)
        );
        rq.headers.emplace(yacare::tests::USER_ID_HEADER, std::to_string(fixture.UID));
        auto resp = yacare::performTestRequest(rq);

        EXPECT_EQ(resp.status, 200);

        std::stringstream fileName;
        fileName << "hotspot_" << layer << "_" << x << "_" << y << "_" << z << ".json";
        ::maps::common::writeFile(fileName.str(), json::prettifyJson(resp.body));

        auto receivedJson = json::Value::fromString(resp.body);
        auto expectedJson = loadExpectedHotspot(layer, x, y, z);
        if (resp.body == EMPTY_HOTSPOT_RESPONSE) {
            EXPECT_EQ(receivedJson, expectedJson);
        }
        else {
            auto receivedFeatures = receivedJson["data"]["features"];
            auto expectedFeatures = expectedJson["data"]["features"];
            EXPECT_TRUE(std::is_permutation(receivedFeatures.begin(),
                                            receivedFeatures.end(),
                                            expectedFeatures.begin(),
                                            expectedFeatures.end()))
                << "response differs for tile " << layer << " " << x << ", "
                << y << ", " << z;
            validateJson(fileName.str(), SCHEMA_PATH);
        }
    }
}

Y_UNIT_TEST(test_zorder_of_mrcss_hotspot_requests)
{
    auto fixture = FixtureFb{};
    auto testRequests = std::vector<TestRequest>{
        {"mrcss", 79222, 41104, 17},
    };
    for (const auto& [layer, x, y, z] : testRequests) {
        http::MockRequest rq(http::GET,
                             http::URL("http://localhost/features/hotspot")
                                 .addParam("x", x)
                                 .addParam("y", y)
                                 .addParam("z", z)
                                 .addParam("l", layer));
        rq.headers.emplace(yacare::tests::USER_ID_HEADER,
                           std::to_string(fixture.UID));
        auto resp = yacare::performTestRequest(rq);
        EXPECT_EQ(resp.status, 200);
        EXPECT_NE(resp.body, EMPTY_HOTSPOT_RESPONSE);
        auto receivedJson = json::Value::fromString(resp.body);
        auto receivedFeatures = receivedJson["data"]["features"];
        EXPECT_TRUE(std::is_sorted(receivedFeatures.begin(),
                                   receivedFeatures.end(),
                                   [](auto& lhs, auto& rhs) {
                                       return featureTime(lhs) <
                                              featureTime(rhs);
                                   }))
            << "response differs for tile " << layer << " " << x << ", " << y
            << ", " << z;
    }
}

Y_UNIT_TEST(test_age_tiles_requests)
{
    using namespace chrono::literals;
    using namespace geolib3;

    const auto SRC = std::string("src");
    const auto NOW = chrono::TimePoint::clock::now();
    const auto MDS = mds::Key("groupId", "path");

    auto features = db::Features{
        db::Feature{SRC,
                    Point2{37.617775, 55.747842},
                    Heading{80},
                    chrono::formatSqlDateTime(NOW - 500_days),
                    MDS,
                    {}},
        db::Feature{SRC,
                    Point2{37.617967, 55.747875},
                    Heading{80},
                    chrono::formatSqlDateTime(NOW - 50_days),
                    MDS,
                    {}},
        db::Feature{SRC,
                    Point2{37.618123, 55.747900},
                    Heading{80},
                    chrono::formatSqlDateTime(NOW - std::chrono::hours(1)),
                    MDS,
                    {}},
        db::Feature{SRC,
                    Point2{37.618123, 55.747900},
                    Heading{260},
                    chrono::formatSqlDateTime(NOW),
                    MDS,
                    {}},
    };

    for (auto& feature : features) {
        feature.setSize(1920, 1080)
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true)
            .setCameraDeviation(db::CameraDeviation::Front)
            .setPrivacy(db::FeaturePrivacy::Public);
    }

    UserMayViewPhotosFixture<true> fixture([&](pgpool3::Pool& pool) {
        auto txn = pool.masterWriteableTransaction();
        db::FeatureGateway{*txn}.insert(features);
        db::updateFeaturesTransaction(features, *txn);
        txn->commit();
        Playground::instance().makeCoverage();
    });

    auto pos = features.front().mercatorPos();
    auto makeRequest = [pos](std::string_view layer, int zoom) {
        auto tile = tile:: mercatorToTile(pos, zoom);
        return TestRequest{layer, tile.x(), tile.y(), tile.z()};
    };

    std::vector requests = {
        makeRequest("mrc_age", 11),
        makeRequest("mrc_age", 13),
        makeRequest("mrc_age", 15),
        makeRequest("mrc_age", 17),
        makeRequest("mrc_age_p", 11),
        makeRequest("mrc_age_p", 13),
        makeRequest("mrc_age_p", 15),
        makeRequest("mrc_age_p", 17),
        makeRequest("mrc_age_r", 11),
        makeRequest("mrc_age_r", 13),
        makeRequest("mrc_age_r", 15),
        makeRequest("mrc_age_r", 17),
    };

    for (const auto& [layer, x, y, z] : requests) {
        http::MockRequest rq(http::GET,
                             http::URL("http://localhost/tiles")
                                 .addParam("x", x)
                                 .addParam("y", y)
                                 .addParam("z", z)
                                 .addParam("l", layer));
        auto resp = yacare::performTestRequest(rq);
        EXPECT_EQ(resp.status, 200);

        std::stringstream fileName;
        fileName << "tiles_" << layer << "_" << x << "_" << y << "_" << z
                 << ".png";
        maps::common::writeFile(fileName.str(), resp.body);

        EXPECT_LT(measureMeanAbsoluteError(
                      img::RasterImage::fromPngBlob(common::toBytes(resp.body)),
                      loadExpectedTile(layer, x, y, z)),
                  MEAN_IMAGE_ERROR)
            << "image differs for tile " << layer << " " << x << ", " << y
            << ", " << z;
    }
}

Y_UNIT_TEST(test_merging_tiles_requests)
{
    using namespace chrono::literals;
    using namespace geolib3;

    static const auto [POS1, POS2, HEADING] = [] {
        auto pos = Point2{37.608014, 55.766909};
        auto edgeId = *graph().rtree().nearestBaseEdges(pos).begin();
        auto edgeData = graph().graph().edgeData(edgeId);
        auto polyline = geolib3::Polyline2(edgeData.geometry());
        auto segment = polyline.segmentAt(0);
        return std::tuple{
            segment.start(),
            segment.midpoint(),
            Direction2{convertGeodeticToMercator(segment)}.heading()};
    }();
    static const auto SRC = std::string("src");
    static const auto NOW = chrono::TimePoint::clock::now();
    static const auto MDS = mds::Key("groupId", "path");

    auto features = db::Features{
        db::Feature{SRC,
                    POS1,
                    HEADING,
                    chrono::formatSqlDateTime(NOW - 1_days),
                    MDS,
                    {}}
            .setCameraDeviation(db::CameraDeviation::Front),
        db::Feature{SRC,
                    POS2,
                    HEADING,
                    chrono::formatSqlDateTime(NOW - 500_days),
                    MDS,
                    {}}
            .setCameraDeviation(db::CameraDeviation::Front),
        db::Feature{SRC,
                    POS1,
                    HEADING,
                    chrono::formatSqlDateTime(NOW - 50_days),
                    MDS,
                    {}}
            .setCameraDeviation(db::CameraDeviation::Right),
        db::Feature{SRC,
                    POS2,
                    HEADING,
                    chrono::formatSqlDateTime(NOW - 10_days),
                    MDS,
                    {}}
            .setCameraDeviation(db::CameraDeviation::Right),
    };
    for (auto& feature : features) {
        feature.setSize(1920, 1080)
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true)
            .setPrivacy(db::FeaturePrivacy::Public);
    }
    auto fixture = UserMayViewPhotosFixture<true>([&](pgpool3::Pool& pool) {
        auto txn = pool.masterWriteableTransaction();
        db::FeatureGateway{*txn}.insert(features);
        db::updateFeaturesTransaction(features, *txn);
        txn->commit();
        Playground::instance().makeCoverage();
    });

    struct {
        int zoom;
        float epsilon;
    } zoomToEpsilon[] = {
        {.zoom = 11, .epsilon = 0.000001},
        {.zoom = 17, .epsilon = 0.2},
    };
    struct {
        std::string cameraDirection;
        std::string prefix;
    } cameraDirectionToPrefix[] = {
        {.cameraDirection = "front", .prefix = "front"},
        {.cameraDirection = "right", .prefix = "right"},
        {.cameraDirection = "", .prefix = "mixed"},
    };
    auto layer = "mrc_age";
    for (const auto [zoom, epsilon] : zoomToEpsilon) {
        for (const auto& [cameraDirection, prefix] : cameraDirectionToPrefix) {
            auto tile =
                tile::mercatorToTile(convertGeodeticToMercator(POS2), zoom);
            auto [x, y, z] = std::tuple{tile.x(), tile.y(), tile.z()};
            auto url = http::URL("http://localhost/tiles")
                           .addParam("x", x)
                           .addParam("y", y)
                           .addParam("z", z)
                           .addParam("l", layer);
            if (!cameraDirection.empty()) {
                url.addParam("camera-direction", cameraDirection);
            }
            http::MockRequest rq(http::GET, url);
            auto resp = yacare::performTestRequest(rq);
            EXPECT_EQ(resp.status, 200);

            auto fileName = std::stringstream{} << prefix + "_tiles_" << layer
                                                << "_" << x << "_" << y << "_"
                                                << z << ".png";
            EXPECT_LT(
                measureMeanAbsoluteError(
                    img::RasterImage::fromPngBlob(common::toBytes(resp.body)),
                    img::RasterImage::fromPngFile(
                        SRC_("expected/test_merging_tiles_requests/" +
                             fileName.str()))),
                epsilon)
                << "image differs for tile " << layer << " .x=" << x
                << ", .y=" << y << ", .z=" << z << " " << cameraDirection;
        }
    }
}

Y_UNIT_TEST(test_age_hotspot_requests)
{
    using namespace geolib3;

    const auto SRC = std::string("src");
    const auto MDS = mds::Key("groupId", "path");

    auto features = db::Features{
        db::Feature{
            SRC, Point2{37.617775, 55.747842}, Heading{80}, "2020-09-01 18:17:00", MDS, {}},
        db::Feature{
            SRC, Point2{37.617967, 55.747875}, Heading{80}, "2020-09-01 19:17:00", MDS, {}},
        db::Feature{
            SRC, Point2{37.618123, 55.747900}, Heading{80}, "2020-09-01 20:17:00", MDS, {}},
        db::Feature{
            SRC, Point2{37.618123, 55.747900}, Heading{260}, "2020-09-01 21:17:00", MDS, {}},
    };

    for (auto& feature : features) {
        feature.setSize(1920, 1080)
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true)
            .setCameraDeviation(db::CameraDeviation::Front)
            .setPrivacy(db::FeaturePrivacy::Public);
    }

    UserMayViewPhotosFixture<true> fixture([&](pgpool3::Pool& pool) {
        auto txn = pool.masterWriteableTransaction();
        db::FeatureGateway{*txn}.insert(features);
        db::updateFeaturesTransaction(features, *txn);
        txn->commit();
        Playground::instance().makeCoverage();
    });

    auto pos = features.front().mercatorPos();
    auto makeRequest = [pos](std::string_view layer, int zoom) {
        auto tile = tile:: mercatorToTile(pos, zoom);
        return TestRequest{layer, tile.x(), tile.y(), tile.z()};
    };

    std::vector requests = {
        makeRequest("mrc_age", 11),
        makeRequest("mrc_age", 17),
        makeRequest("mrc_age_p", 11),
        makeRequest("mrc_age_p", 17),
        makeRequest("mrc_age_r", 11),
        makeRequest("mrc_age_r", 17),
    };

    const std::string SCHEMA_PATH =
        apiResponseSchemaPath("browser/get_hotspots.response.schema.json");
    for (const auto& [layer, x, y, z] : requests) {
        http::MockRequest rq(http::GET,
                             http::URL("http://localhost/features/hotspot")
                                 .addParam("x", x)
                                 .addParam("y", y)
                                 .addParam("z", z)
                                 .addParam("l", layer));
        auto resp = yacare::performTestRequest(rq);
        EXPECT_EQ(resp.status, 200);

        std::stringstream fileName;
        fileName << "hotspot_" << layer << "_" << x << "_" << y << "_" << z << ".json";
        ::maps::common::writeFile(fileName.str(), json::prettifyJson(resp.body));

        auto receivedJson = json::Value::fromString(resp.body);
        auto expectedJson = loadExpectedHotspot(layer, x, y, z);
        if (resp.body == EMPTY_HOTSPOT_RESPONSE) {
            EXPECT_EQ(receivedJson, expectedJson);
        }
        else {
            auto receivedFeatures = receivedJson["data"]["features"];
            auto expectedFeatures = expectedJson["data"]["features"];
            EXPECT_TRUE(std::is_permutation(receivedFeatures.begin(),
                                            receivedFeatures.end(),
                                            expectedFeatures.begin(),
                                            expectedFeatures.end()))
                << "response differs for tile " << layer << " " << x << ", "
                << y << ", " << z;
            validateJson(fileName.str(), SCHEMA_PATH);
        }
    }
}

template <class Fixture>
int testTiles()
{
    auto TILE = tile::Tile{618, 321, 10};
    Fixture fixture(
        [=](pgpool3::Pool& pool) {
            fill(pool, mercatorBBox(TILE), Fixture::RegionPrivacy);
        }
    );

    http::MockRequest rq(http::GET,
                         http::URL("http://localhost/tiles")
                             .addParam("x", TILE.x())
                             .addParam("y", TILE.y())
                             .addParam("z", TILE.z())
                             .addParam("l", "mrcss"));
    rq.headers.emplace(yacare::tests::USER_ID_HEADER, std::to_string(fixture.UID));
    auto resp = yacare::performTestRequest(rq);
    return resp.status;
}

Y_UNIT_TEST(test_tiles_covered_edge)
{
    EXPECT_EQ(testTiles<RegularUserAccessSecretRegionFixture>(), 204);
    EXPECT_EQ(testTiles<OutsourcerUserAccessSecretRegionFixture>(), 204);
    EXPECT_EQ(testTiles<TrustedUserAccessSecretRegionFixture>(), 200);
    EXPECT_EQ(testTiles<RegularUserAccessRestrictedRegionFixture>(), 204);
    EXPECT_EQ(testTiles<OutsourcerUserAccessRestrictedRegionFixture>(), 200);
    EXPECT_EQ(testTiles<TrustedUserAccessRestrictedRegionFixture>(), 200);
    EXPECT_EQ(testTiles<RegularUserAccessPublicRegionFixture>(), 200);
    EXPECT_EQ(testTiles<OutsourcerUserAccessPublicRegionFixture>(), 200);
    EXPECT_EQ(testTiles<TrustedUserAccessPublicRegionFixture>(), 200);
}

Y_UNIT_TEST(test_tiles_covered_edge_for_trusted_user_overview_zoom)
{
    auto TILE = tile::Tile{1238, 640, 11};
    UserMayViewPhotosFixture<true> fixture(
        [=] (pgpool3::Pool& pool) {
            fill(pool, mercatorBBox(TILE), db::FeaturePrivacy::Restricted);
        }
    );

    http::MockRequest rq(http::GET,
                         http::URL("http://localhost/tiles")
                             .addParam("x", TILE.x())
                             .addParam("y", TILE.y())
                             .addParam("z", TILE.z())
                             .addParam("l", "mrcss"));
    rq.headers.emplace(yacare::tests::USER_ID_HEADER, std::to_string(fixture.UID));
    auto resp = yacare::performTestRequest(rq);
    EXPECT_EQ(resp.status, 200);
}

Y_UNIT_TEST(test_tiles_covered_edge_for_trusted_user_mcre_overview_zoom)
{
    auto TILE = tile::Tile{1238, 640, 11};
    UserMayViewPhotosFixture<true> fixture(
        [=] (pgpool3::Pool& pool) { fill(pool, mercatorBBox(TILE), db::FeaturePrivacy::Restricted); });

    http::MockRequest rq(http::GET,
                         http::URL("http://localhost/tiles")
                             .addParam("x", TILE.x())
                             .addParam("y", TILE.y())
                             .addParam("z", TILE.z())
                             .addParam("l", "mrce"));
    rq.headers.emplace(yacare::tests::USER_ID_HEADER, std::to_string(fixture.UID));
    auto resp = yacare::performTestRequest(rq);
    // should be empty
    EXPECT_EQ(resp.status, 204);
}


Y_UNIT_TEST(test_filter_by_act_date)
{
    FixtureFb fixture;

    const std::string layer = "mrce_cov";
    int x = 79222;
    int y = 41104;
    int z = 17;

    struct Request {
        std::optional<std::string> actualizedAfter;
        std::optional<std::string> actualizedBefore;
        bool emptyResponse;
    };

    std::vector<Request> testRequests = {
        {std::nullopt, std::nullopt, false},
        {"2016-04-01", std::nullopt, false},
        {"2016-04-02", std::nullopt, true},
        {"2016-04-01", "2016-04-02", false},
        {std::nullopt, "2016-04-01", true}
    };

    for (const auto& testRequest : testRequests)
    {
        auto url = http::URL("http://localhost/tiles")
                .addParam("x", x)
                .addParam("y", y)
                .addParam("z", z)
                .addParam("l", layer);

        if (testRequest.actualizedAfter.has_value()) {
            url.addParam("actualized-after", testRequest.actualizedAfter.value());
        }

        if (testRequest.actualizedBefore.has_value()) {
            url.addParam("actualized-before", testRequest.actualizedBefore.value());
        }

        http::MockRequest rq(http::GET, url);

        auto resp = yacare::performTestRequest(rq);

        if (testRequest.emptyResponse) {
            EXPECT_EQ(resp.status, 204);
            continue;
        }

        EXPECT_EQ(resp.status, 200);

        std::stringstream fileName;
        fileName << "tiles_" << layer << "_" << x << "_" << y << "_" << z << ".png";
        ::maps::common::writeFile(fileName.str(), resp.body);

        EXPECT_LT(measureMaxAbsoluteError(
                      img::RasterImage::fromPngBlob(common::toBytes(resp.body)),
                      loadExpectedTile(layer, x, y, z)),
                  MAX_IMAGE_ERROR
        ) << "image differs for url " << url;
    }
}


Y_UNIT_TEST(test_tiles_protobuf_formats_render)
{
    FixtureFb fixture;

    auto commonUrl = http::URL("http://localhost/tiles")
                .addParam("x", 79222)
                .addParam("y", 41104)
                .addParam("z", 17)
                .addParam("l", "mrce");


    auto protoUrl = commonUrl;
    protoUrl.addParam("format", "protobuf");
    http::MockRequest protoRq(http::GET, protoUrl);
    auto protoResp = yacare::performTestRequest(protoRq);
    EXPECT_EQ(protoResp.status, 200);
    EXPECT_EQ(protoResp.headers["Content-Type"], "application/x-protobuf");

    TileV2 tileV2;
    Y_PROTOBUF_SUPPRESS_NODISCARD tileV2.ParseFromArray(protoResp.body.data(), protoResp.body.size());
    EXPECT_TRUE(tileV2.IsInitialized());

    auto textUrl = commonUrl;
    textUrl.addParam("format", "text");
    http::MockRequest textRq(http::GET, textUrl);
    auto textResp = yacare::performTestRequest(textRq);
    EXPECT_EQ(textResp.status, 200);
    EXPECT_EQ(textResp.headers["Content-Type"], "text/x-protobuf");

    EXPECT_EQ(textResp.body, std::string(tileV2.DebugString()));
}


Y_UNIT_TEST(test_tiles_support_accept_render)
{
    FixtureFb fixture;

    const std::string ACCEPT_HEADER = "Accept";
    const std::string layer = "mrce";
    int x = 79222;
    int y = 41104;
    int z = 17;
    auto url = http::URL("http://localhost/tiles")
                .addParam("x", x)
                .addParam("y", y)
                .addParam("z", z)
                .addParam("l", layer);

    http::MockRequest protoRq(http::GET, url);
    protoRq.headers.emplace(ACCEPT_HEADER, "application/x-protobuf");
    auto protoResp = yacare::performTestRequest(protoRq);

    EXPECT_EQ(protoResp.status, 200);
    EXPECT_EQ(protoResp.headers["Content-Type"], "application/x-protobuf");

    TileV2 tileV2;
    Y_PROTOBUF_SUPPRESS_NODISCARD tileV2.ParseFromArray(protoResp.body.data(), protoResp.body.size());
    EXPECT_TRUE(tileV2.IsInitialized());

    http::MockRequest textRq(http::GET, url);
    textRq.headers.emplace(ACCEPT_HEADER, "text/x-protobuf");
    auto textResp = yacare::performTestRequest(textRq);
    EXPECT_EQ(textResp.status, 200);
    EXPECT_EQ(textResp.headers["Content-Type"], "text/x-protobuf");

    EXPECT_EQ(textResp.body, std::string(tileV2.DebugString()));

    http::MockRequest imgRq(http::GET, url);
    imgRq.headers.emplace(ACCEPT_HEADER, "image/png");
    auto imageResp = yacare::performTestRequest(imgRq);
    EXPECT_EQ(imageResp.status, 200);
    EXPECT_EQ(imageResp.headers["Content-Type"], "image/png");

    EXPECT_LT(measureMaxAbsoluteError(img::RasterImage::fromPngBlob(
                                          common::toBytes(imageResp.body)),
                                      loadExpectedTile(layer, x, y, z)),
              MAX_IMAGE_ERROR
    ) << "image differs for url " << url;

    http::MockRequest rq(http::GET, url);
    rq.headers.emplace(ACCEPT_HEADER, "*/*");
    auto resp = yacare::performTestRequest(rq);
    EXPECT_EQ(resp.status, 200);
    EXPECT_EQ(resp.headers["Content-Type"], "image/png");

    EXPECT_LT(measureMaxAbsoluteError(img::RasterImage::fromPngBlob(
                                          common::toBytes(resp.body)),
                                      loadExpectedTile(layer, x, y, z)),
              MAX_IMAGE_ERROR
    ) << "image differs for url " << url;
}

Y_UNIT_TEST(test_tiles_protobuf_versions_render)
{
    FixtureFb fixture;

    auto commonUrl = http::URL("http://localhost/tiles")
                .addParam("x", 79222)
                .addParam("y", 41104)
                .addParam("z", 17)
                .addParam("l", "mrce")
                .addParam("format", "protobuf");

    auto v2Url = commonUrl;
    v2Url.addParam("vec_protocol", "2");
    http::MockRequest v2Rq(http::GET, v2Url);
    auto v2Resp = yacare::performTestRequest(v2Rq);
    EXPECT_EQ(v2Resp.status, 200);
    EXPECT_EQ(v2Resp.headers["Content-Type"], "application/x-protobuf");

    TileV2 tileV2;
    Y_PROTOBUF_SUPPRESS_NODISCARD tileV2.ParseFromArray(v2Resp.body.data(), v2Resp.body.size());
    EXPECT_TRUE(tileV2.IsInitialized());

    auto v3Url = commonUrl;
    v3Url.addParam("vec_protocol", "3");
    http::MockRequest v3Rq(http::GET, v3Url);
    auto v3Resp = yacare::performTestRequest(v3Rq);
    EXPECT_EQ(v3Resp.status, 200);
    EXPECT_EQ(v3Resp.headers["Content-Type"], "application/x-protobuf");

    TileV3 tileV3;
    Y_PROTOBUF_SUPPRESS_NODISCARD tileV3.ParseFromArray(v3Resp.body.data(), v3Resp.body.size());
    EXPECT_TRUE(tileV3.IsInitialized());
}
/* todo:
Y_UNIT_TEST(test_tiles_support_multizoom)
{
    FixtureFb fixture;

    const std::string ACCEPT_HEADER = "Accept";
    const std::string layer = "mrce";
    const int x = 79222;
    const int y = 41104;
    const int z = 17;
    const int zmin = z - 1;
    const int zmax = z + 1;
    auto url = http::URL("http://localhost/tiles")
                .addParam("x", x)
                .addParam("y", y)
                .addParam("z", z)
                .addParam("l", layer)
                .addParam("zmin", zmin)
                .addParam("zmax", zmax);

    http::MockRequest protoRq(http::GET, url);
    protoRq.headers.emplace(ACCEPT_HEADER, "application/x-protobuf");
    auto protoResp = yacare::performTestRequest(protoRq);

    ASSERT_EQ(protoResp.status, 200);
    EXPECT_EQ(protoResp.headers["Content-Type"], "application/x-protobuf");

    TileV2 tileV2;
    tileV2.ParseFromArray(protoResp.body.data(), protoResp.body.size());
    ASSERT_TRUE(tileV2.IsInitialized());

    EXPECT_EQ(std::string(tileV2.DebugString()),
              loadExpectedTileTxt(layer, x, y, z, zmin, zmax));

}
*/
Y_UNIT_TEST(test_filter_by_privacy_for_uncovered_edges)
{

    const auto TILE = tile::Tile{19808, 10274, 15};

    const auto RESTRICTED_TILE = tile::Tile{2 * TILE.x(), 2 * TILE.y() + 1, TILE.z() + 1};
    const auto SECRET_TILE = tile::Tile{2 * TILE.x() + 1, 2 * TILE.y() + 1, TILE.z() + 1};

    const auto SECRET_MERC_BBOX = mercatorBBox(SECRET_TILE);
    const auto RESTRICTED_MERC_BBOX = mercatorBBox(RESTRICTED_TILE);

    Fixture fixture([=](pgpool3::Pool&) {});

    auto regionPrivacy = privacy::makeMockRegionPrivacy();
    ON_CALL(*regionPrivacy,
            evalMinFeaturePrivacy(
                testing::Matcher<const geolib3::BoundingBox&>(testing::_)))
        .WillByDefault(testing::Return(db::FeaturePrivacy::Public));

    ON_CALL(*regionPrivacy, evalMaxFeaturePrivacy).WillByDefault(
        [&](const geolib3::BoundingBox& geoBox){
            auto mercBox = geolib3::convertGeodeticToMercator(geoBox);
            if (geolib3::spatialRelation(SECRET_MERC_BBOX, mercBox, geolib3::Intersects)) {
                return db::FeaturePrivacy::Secret;
            } else if (geolib3::spatialRelation(RESTRICTED_MERC_BBOX, mercBox, geolib3::Intersects)) {
                return db::FeaturePrivacy::Restricted;
            }
            return db::FeaturePrivacy::Public;
        }
    );

    Configuration::instance()->setRegionPrivacy(std::move(regionPrivacy));

    http::MockRequest rq(http::GET,
                         http::URL("http://localhost/tiles")
                             .addParam("x", TILE.x())
                             .addParam("y", TILE.y())
                             .addParam("z", TILE.z())
                             .addParam("l", "mrcpe"));
    rq.headers.emplace(yacare::tests::USER_ID_HEADER, std::to_string(fixture.UID));
    auto resp = yacare::performTestRequest(rq);

    EXPECT_EQ(resp.status, 200);

    std::string fileName("filter_by_privacy_for_uncovered_edges_tile.png");
    ::maps::common::writeFile(fileName, resp.body);

    auto expectedImage = img::RasterImage::fromPngFile(SRC_("expected/" + fileName));

    EXPECT_LT(measureMaxAbsoluteError(
                  img::RasterImage::fromPngBlob(common::toBytes(resp.body)),
                  expectedImage),
              MAX_IMAGE_ERROR);
}

Y_UNIT_TEST(test_data_schema_consistency)
{
    Fixture fixture;

    const auto runTestForLayer = [](const std::string& layer) {
        http::MockRequest rq(http::GET, http::URL("http://localhost/tiles")
                                 .addParam("x", 79222)
                                 .addParam("y", 41104)
                                 .addParam("z", 17)
                                 .addParam("l", layer)
                                 .addParam("vec_protocol", 3)
                                 .addParam("format", "protobuf"));
        auto resp = yacare::performTestRequest(rq);
        ASSERT_EQ(resp.status, 200);

        renderer::data_set_test_util::matchSchema(
            layerDataSchemaPath(layer),
            renderer::data_set_test_util::generateSchema({resp.body}));
    };

    runTestForLayer(LAYER_EDGES);
    runTestForLayer(LAYER_PEDESTRIAN_EDGES);
}


} // Y_UNIT_TEST_SUITE(render_api_should)

} // namespace maps::mrc::browser::tests
