#include <maps/wikimap/mapspro/services/mrc/browser/tests/fixture.h>
#include <maps/wikimap/mapspro/services/mrc/browser/lib/types.h>

#include <maps/wikimap/mapspro/services/mrc/browser/lib/configuration.h>
#include <yandex/maps/mrc/unittest/utils.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/panorama_utils.h>

#include <maps/libs/http/include/test_utils.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/common/include/file_utils.h>
#include <maps/infra/yacare/include/test_utils.h>

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

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

const std::string TEST_PANO_MDS_KEY = "f261549";
const common::PanoCutParams TEST_PANO_CUT_PARAMS {1792, 896, 256, 256, 2};
const float TEST_IMAGE_SCALE = 1.f;

// Panorama data is located at:
// http://s3.mds.yandex.net/pano-src/img/b2faa382e4e729a3957779158d34b1fb
// To download use the following script:
// for X in {0..21}; do for Y in {0..10}; do wget\
//     http://s3.mds.yandex.net/pano/f261549/0.$X.$Y; done; done


http::URL
makePanoGropOutUrl(const std::string& postfix,
                   const std::string& mdsKey,
                   const common::PanoCutParams& cutParams,
                   const common::PanoCropOut cropOut,
                   const common::ImageBoxes& iBoxes)
{
    http::URL url {"http://localhost/pano/" + mdsKey + "/" + postfix};
    url.addParam("cut_params", boost::lexical_cast<std::string>(cutParams));
    url.addParam("crop_out", boost::lexical_cast<std::string>(cropOut));

    std::string boxes;
    std::string semicolon = "";
    for (const auto& iBox : iBoxes) {
        boxes += semicolon
            + std::to_string(iBox.minX()) + "," + std::to_string(iBox.minY())
            + ","
            + std::to_string(iBox.maxX()) + "," + std::to_string(iBox.maxY());
        semicolon = ";";
    }
    if (!boxes.empty()) {
        url.addParam("boxes", boxes);
    }

    return url;
}

cv::Mat expectedImage(const std::string& filename)
{
    const auto encodedImage
        = maps::common::readFileToString(SRC_("expected") + "/" + filename);
    return common::decodeImage(encodedImage);
}

auto requestPanoCropOut(const std::string& postfix,
                        const common::PanoCropOut& cropOut,
                        const common::PanoCutParams& cutParams = TEST_PANO_CUT_PARAMS,
                        const common::ImageBoxes& iBoxes = {})
{
    http::MockRequest rq {http::GET,
        makePanoGropOutUrl(
            postfix, TEST_PANO_MDS_KEY, cutParams, cropOut, iBoxes)};
    return yacare::performTestRequest(rq);
}

} // namespace

Y_UNIT_TEST_SUITE_F(panorama_api_should, Fixture)
{

Y_UNIT_TEST(test_middle_panorama_image) {
    auto resp = requestPanoCropOut(
        "image",
        {726, 280, 597, 336, TEST_IMAGE_SCALE},
        TEST_PANO_CUT_PARAMS,
        {{100, 100, 200, 200}, {300, 300, 400, 400}});
    // Crop out an image from the middle of the panorama and draw some bboxes.
    UNIT_ASSERT_EQUAL(resp.status, 200);
    const auto expected = expectedImage("pano_middle_panorama_image.jpg");
    EXPECT_TRUE(
        unittest::areImagesEq(common::decodeImage(resp.body), expected));
}

Y_UNIT_TEST(test_cross_side_edge_panorama_image) {
    auto resp = requestPanoCropOut(
        "image", {1493, 280, 597, 336, TEST_IMAGE_SCALE});
    // Grop out from the panorama so that it's side edge gets into the middle
    // of ghe resulting image.
    UNIT_ASSERT_EQUAL(resp.status, 200);
    const auto expected = expectedImage("pano_cross_side_edge_panorama_image.jpg");
    EXPECT_TRUE(
        unittest::areImagesEq(common::decodeImage(resp.body), expected));
}

Y_UNIT_TEST(test_middle_panorama_thumbnail) {
    auto resp = requestPanoCropOut(
        "thumbnail", {726, 280, 597, 336, TEST_IMAGE_SCALE});
    // Crop out from the middle of the panorama.
    UNIT_ASSERT_EQUAL(resp.status, 200);
    const auto expected
        = expectedImage("pano_middle_panorama_thumbnail.jpg");
    EXPECT_TRUE(
        unittest::areImagesEq(common::decodeImage(resp.body), expected));
}

Y_UNIT_TEST(test_top_edge_panorama_thumbnail) {
    auto resp = requestPanoCropOut(
        "thumbnail", {726, 0, 597, 336, TEST_IMAGE_SCALE});
    // Crop out from the bottom of the panorama.
    UNIT_ASSERT_EQUAL(resp.status, 200);
    const auto expected
        = expectedImage("pano_top_edge_panorama_thumbnail.jpg");
    EXPECT_TRUE(
        unittest::areImagesEq(common::decodeImage(resp.body), expected));
}

Y_UNIT_TEST(test_bottom_edge_panorama_thumbnail) {
    auto resp = requestPanoCropOut(
        "thumbnail", {726, 560, 597, 336, TEST_IMAGE_SCALE});
    // Crop out from the top of the panorama.
    UNIT_ASSERT_EQUAL(resp.status, 200);
    const auto expected
        = expectedImage("pano_bottom_edge_panorama_thumbnail.jpg");
    EXPECT_TRUE(
        unittest::areImagesEq(common::decodeImage(resp.body), expected));
}

Y_UNIT_TEST(test_full_panorama_image) {
    auto resp = requestPanoCropOut("image", {128, 0, 1792, 896, 0.33});
    // Load the full panorama image.
    UNIT_ASSERT_EQUAL(resp.status, 200);
    const auto expected = expectedImage("pano_full_image.jpg");
    EXPECT_TRUE(
        unittest::areImagesEq(common::decodeImage(resp.body), expected));
}

Y_UNIT_TEST(test_one_pixel_panorama_image) {
    auto resp = requestPanoCropOut("image", {0, 0, 1, 1, 1});
    // Load one pixel from the panorama image.
    UNIT_ASSERT_EQUAL(resp.status, 200);
    cv::Mat actual = common::decodeImage(resp.body);
    EXPECT_EQ(actual.cols, 1);
    EXPECT_EQ(actual.rows, 1);
}

Y_UNIT_TEST(test_crop_out_is_out_of_horizontal_bounds) {
    auto resp = requestPanoCropOut(
        "thumbnail", {1792, 0, 256, 256, TEST_IMAGE_SCALE});
    // Crop out left top corner is outside of the panorama image horizontal
    // dimension
    EXPECT_EQ(resp.status, 400);
}

Y_UNIT_TEST(test_crop_out_is_out_of_vertical_bounds)
{
    auto resp = requestPanoCropOut(
        "thumbnail", {0, 897, 256, 256, TEST_IMAGE_SCALE});
    // Crop out left top corner is outside of the panorama image vertical
    // dimension
    EXPECT_EQ(resp.status, 400);
}

Y_UNIT_TEST(test_zero_crop_out) {
    auto resp = requestPanoCropOut(
        "thumbnail", {0, 0, 0, 0, TEST_IMAGE_SCALE});
    // Crop out width exceeds total panorama width
    EXPECT_EQ(resp.status, 400);
}

Y_UNIT_TEST(test_crop_out_is_too_wide) {
    auto resp = requestPanoCropOut(
        "thumbnail", {0, 0, 1793, 896, TEST_IMAGE_SCALE});
    // Crop out width exceeds total panorama width
    EXPECT_EQ(resp.status, 400);
}

Y_UNIT_TEST(test_crop_out_is_too_tall) {
    auto resp = requestPanoCropOut(
        "thumbnail", {0, 0, 1792, 897, TEST_IMAGE_SCALE});
    // Crop out height exceeds total panorama height
    EXPECT_EQ(resp.status, 400);
}

Y_UNIT_TEST(test_image_scale_out_of_bouns) {
    auto resp = requestPanoCropOut(
        "thumbnail", {0, 0, 256, 256, 0.f});
    EXPECT_EQ(resp.status, 400);

    resp = requestPanoCropOut(
        "thumbnail", {0, 0, 256, 256, 1.00001f});
    EXPECT_EQ(resp.status, 400);
}

} // Y_UNIT_TEST_SUITE_F(panorama_api_should, PanoramaTestsFixture)
} // namespace maps::mrc::browser::tests
