#include <library/cpp/testing/common/env.h>
#include <library/cpp/testing/gtest/gtest.h>
#include <maps/libs/common/include/file_utils.h>
#include <maps/libs/img/include/algorithm.h>
#include <maps/libs/img/include/raster.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/algorithm/for_each_batch.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/exif.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/opencv.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/suncalc.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/yacare_helpers.h>
#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <yandex/maps/mrc/unittest/database_fixture.h>
#include <yandex/maps/mrc/unittest/utils.h>

#include <opencv2/opencv.hpp>

#include <fstream>
#include <numeric>
#include <sstream>
#include <vector>

namespace maps::mrc::common::tests {
using namespace ::testing;

struct Fixture : testing::Test,
                 unittest::WithUnittestConfig<unittest::DatabaseFixture> {
};

TEST_F(Fixture, test_pool_holder) {
    auto holder = config().makePoolHolder();
    auto txn = holder.pool().masterReadOnlyTransaction();
    auto res = txn->exec("SELECT COUNT(*) FROM signals.feature;");
    EXPECT_EQ(res.front().front().as<size_t>(), 0u);
}

TEST(Common_should, test_for_each_batch) {
    std::vector<int> in = {1, 2, 3, 4, 5};
    std::vector<int> out;
    forEachBatch(in, 2, [&](std::vector<int>::iterator begin,
        std::vector<int>::iterator end) {
        out.push_back(std::accumulate(begin, end, 0));
    });
    std::vector<int> expected = {3, 7, 5};
    EXPECT_EQ(out, expected);
}

// The results checked with http://voshod-solnca.ru/
// match to a precision of several minutes
TEST(Common_should, test_sunrise_sunset_hours) {
    constexpr double EPS = 0.01; // in percent

    // Moscow
    auto[sunrise, sunset] = getSunriseSunsetHours(18, 9, 2016, 55.73, 37.58);
    EXPECT_NEAR(sunrise, 3.1356, EPS); // sunrise at ~6:08 local time
    EXPECT_NEAR(sunset, 15.6316, EPS); // sunset at ~18:38 local time

    // Saint-Petersburg
    std::tie(sunrise, sunset) = getSunriseSunsetHours(22, 06, 2016, 59.9346, 30.3351);
    EXPECT_NEAR(sunrise, 0.592289, EPS); // sunrise at ~3:35 local time
    EXPECT_NEAR(sunset, 19.4313, EPS); // sunset at ~22:26 local time

    // New Jersey
    std::tie(sunrise, sunset) = getSunriseSunsetHours(25, 6, 1990, 40.9, -74.3);
    EXPECT_NEAR(sunrise, 9.4414, EPS);
    EXPECT_NEAR(sunset, 0.5498, EPS);

    // Sydney
    std::tie(sunrise, sunset) = getSunriseSunsetHours(03, 03, 2016, -33.8741, 151.2158);
    EXPECT_NEAR(sunrise, 19.7496, EPS);
    EXPECT_NEAR(sunset, 8.47763, EPS);

    // Beyond Polar Circle
    std::tie(sunrise, sunset) = getSunriseSunsetHours(22, 06, 2016, 76.7861, 67.7345);
    EXPECT_EQ(sunset, NO_SUNSET);

    std::tie(sunrise, sunset) = getSunriseSunsetHours(22, 12, 2016, 76.7861, 67.7345);
    EXPECT_EQ(sunrise, NO_SUNRISE);

    // Boundary between non-polar day and polar day (Greenland))
    std::tie(sunrise, sunset) = getSunriseSunsetHours(24, 05, 2016, 68., -35.);
    EXPECT_NEAR(sunrise, 2.8910, EPS);
    EXPECT_NEAR(sunset, 1.79107, EPS);

    std::tie(sunrise, sunset) = getSunriseSunsetHours(25, 05, 2016, 68., -35.);
    EXPECT_EQ(sunset, NO_SUNSET);
}

TEST(Common_should, test_opencv) {
    EXPECT_GT(estimateImageQuality(maps::common::readFileToString("opencv_1.jpg")), .5); // good image
    EXPECT_LT(estimateImageQuality(maps::common::readFileToString("opencv_2.jpg")), .5); // bad image

    static const auto PNG_IMAGE_STRING = getTestImage<std::string>();
    // JPEG_HEADER contains two first segments of the jpeg format
    static const auto JPEG_HEADER_VEC = Bytes{0xff, 0xd8, 0xff, 0xe0};
    static const auto JPEG_HEADER = std::string{JPEG_HEADER_VEC.begin(), JPEG_HEADER_VEC.end()};
    auto equalizedImage = equalizeHistogram(PNG_IMAGE_STRING);
    EXPECT_EQ(JPEG_HEADER, std::string(equalizedImage.begin(), JPEG_HEADER.size()));

    auto originalImage = mrc::common::decodeImage(maps::common::readFileToString("opencv_1.jpg"));
    auto expectedBlurredImage = mrc::common::decodeImage(
        maps::common::readFileToString("opencv_1_blurred_ellipse.png"));

    // Normal blur check
    auto normalBlur = originalImage.clone();
    ellipticalBlur(normalBlur, {{0, 0}, normalBlur.size()});
    EXPECT_TRUE(unittest::areImagesEq(normalBlur, expectedBlurredImage));

    // Check bboxes that span out of bounds of an image don't cause crashes
    auto oobBlur = originalImage.clone();
    ellipticalBlur(oobBlur, {{-100, -100}, oobBlur.size() * 2});
    EXPECT_TRUE(unittest::areImagesEq(oobBlur, expectedBlurredImage));

    // Check bboxes of size less then 5 pixels don't cause crashes
    auto tinyBlur = originalImage.clone();
    ellipticalBlur(tinyBlur, {{0, 0}, cv::Size{4, 4}});
    EXPECT_TRUE(unittest::areImagesEq(tinyBlur, originalImage));
}

TEST(Common_should, test_handle_yandex_origin)
{
    struct {
        std::string origin;
        bool allowed;
    } tests[] = {
        {"http://thing.yandex.ru", true},
        {"http://thing.yandex.ru:80", true},
        {"http://thing.yandex.rus", false},
        {"https://thing.yandex.com.tr:443", true},
        {"http://thing.yandex.com.hacker:443", false},
        {"http://yandex.ru", true},
        {"http://hacker-yandex.ru", false},
        {"http://yandex.az", true},
        {"http://yandex.by", true},
        {"http://yandex.co.il", true},
        {"http://yandex.com.am", true},
        {"http://yandex.com.ge", true},
        {"http://yandex.ee", true},
        {"http://yandex.fi", true},
        {"http://yandex.fr", true},
        {"http://yandex.kg", true},
        {"http://yandex.lt", true},
        {"http://yandex.lv", true},
        {"http://yandex.md", true},
        {"http://yandex.pl", true},
        {"http://yandex.tj", true},
        {"http://yandex.tm", true},
        {"http://yandex.ua", true},
        {"http://yandex.uz", true},
        {"http://yandex-team.ru", true},
        {"http://yandex-team.ru:5432", true},
    };
    for (const auto& [origin, allowed] : tests) {
        auto bld = yacare::RequestBuilder{};
        bld.putenv("HTTP_ORIGIN", origin);
        auto resp = yacare::Response{};
        EXPECT_EQ(handleYandexOrigin(bld.request(), resp), allowed);
        const auto& headers = resp.headers();
        auto originIt = headers.find("Access-Control-Allow-Origin");
        auto credentialsIt = headers.find("Access-Control-Allow-Credentials");
        if (allowed) {
            EXPECT_TRUE(originIt != headers.end() &&
                        std::find(originIt->second.begin(),
                                  originIt->second.end(),
                                  origin) != originIt->second.end() &&
                        credentialsIt != headers.end() &&
                        std::find(credentialsIt->second.begin(),
                                  credentialsIt->second.end(),
                                  "true") != credentialsIt->second.end());
        }
        else {
            EXPECT_TRUE(originIt == headers.end() &&
                        credentialsIt == headers.end());
        }
    }
}

TEST(fitImageTo, should_fit) {
    const cv::Mat in(cv::Size(200, 100), CV_8UC3);

    EXPECT_THAT(fitImageTo(in, Size{100, 100}).size(), cv::Size(100, 50));
    EXPECT_THAT(fitImageTo(in, Size{200, 50}).size(),  cv::Size(100, 50));
    EXPECT_THAT(fitImageTo(in, Size{100, 50}).size(),  cv::Size(100, 50));
}

TEST(fitImageTo, should_not_enlarge) {
    const cv::Mat in(cv::Size(200, 100), CV_8UC3);

    EXPECT_THAT(fitImageTo(in, Size{250, 150}).size(), in.size());
    EXPECT_THAT(fitImageTo(in, Size{200, 100}).size(), in.size());
}

TEST(fitImageTo, should_keep_type) {
    EXPECT_THAT(fitImageTo(cv::Mat(cv::Size(100, 100), CV_8UC3),  Size{50, 50}).type(), CV_8UC3);
    EXPECT_THAT(fitImageTo(cv::Mat(cv::Size(100, 100), CV_16SC2), Size{50, 50}).type(), CV_16SC2);
}

TEST(fitImageTo, should_not_rotate_or_flip) {
    EXPECT_TRUE(
        unittest::areImagesEq(
            fitImageTo(cv::imread(std::string(SRC_("images/arrows_640x480.png"))), Size{320, 240}),
            cv::imread(std::string(SRC_("images/arrows_320x240.png")))
        )
    );
}

TEST(fitImageTo, should_throw_if_image_size_is_wrong) {
    EXPECT_THROW(
        fitImageTo(cv::Mat(cv::Size(100, 0), CV_8UC3), Size{100, 100}),
        maps::RuntimeError
    );
    EXPECT_THROW(
        fitImageTo(cv::Mat(cv::Size(0, 100), CV_8UC3), Size{100, 100}),
        maps::RuntimeError
    );
}

TEST(fitImageTo, should_throw_if_window_size_is_wrong) {
    EXPECT_THROW(
        fitImageTo(cv::Mat(cv::Size(100, 100), CV_8UC3), Size{0, 100}),
        maps::LogicError
    );
    EXPECT_THROW(
        fitImageTo(cv::Mat(cv::Size(100, 100), CV_8UC3), Size{100, 0}),
        maps::LogicError
    );
}

} // namespace maps::mrc::common::tests
