#include "helpers.h"

#include <library/cpp/testing/unittest/registar.h>
#include <yandex/maps/wiki/revision/revisionsgateway.h>
#include <yandex/maps/wiki/tasks/tool_commands.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/wikimap/mapspro/services/tasks_feedback/src/sprav_pedestrian_feedback/lib/entrances/process_entrance.h>

#include <sstream>

namespace maps::wiki::sprav_feedback::tests {

namespace {
const double COMPARE_MERC_EPSILON = 0.01;

std::string toString(const PublishData& etalon)
{
    std::stringstream retVal;
    if (std::holds_alternative<FeedbackPublishData>(etalon)) {
        retVal << "FeedbackPublishData: {";
        {
            const FeedbackPublishData& etalonData = std::get<FeedbackPublishData>(etalon);
            retVal << "reason: " << etalonData.reason;
            retVal << ", entrance: {";
            {
                const auto& entrance = etalonData.entrance;
                retVal << "name: '" << entrance.name << "'";
                retVal << ", pointGeo: " << geolib3::WKT::toString(entrance.pointGeo);
            }
            retVal << "}";
        }
        retVal << "}";
    } else if (std::holds_alternative<CommitPublishData>(etalon)) {
        retVal << "CommitPublishData: {";
        {
            const CommitPublishData& etalonData = std::get<CommitPublishData>(etalon);
            if (std::holds_alternative<geolib3::Point2>(etalonData.entrance)) {
                const geolib3::Point2& etalonPoint = std::get<geolib3::Point2>(etalonData.entrance);
                retVal << "entrance: " << geolib3::WKT::toString(etalonPoint);
            } else if (std::holds_alternative<revision::DBID>(etalonData.entrance)) {
                retVal << "entrance: " << std::get<revision::DBID>(etalonData.entrance);
            } else {
                retVal << "entrance: UNKNOWN";
            }
            retVal << ", name: '" << etalonData.entranceName << "'";
        }
        retVal << "}";
    } else if (std::holds_alternative<SkipReason>(etalon)) {
        retVal << "SkipReason: " << std::get<SkipReason>(etalon);
    } else {
        retVal << "UNKNOWN";
    }
    return retVal.str();
}

void comparePublishData(const PublishData& etalon, const PublishData& object)
{
    try {
        if (std::holds_alternative<FeedbackPublishData>(etalon)) {
            UNIT_ASSERT(std::holds_alternative<FeedbackPublishData>(object));
            const FeedbackPublishData& etalonData = std::get<FeedbackPublishData>(etalon);
            const FeedbackPublishData& objectData = std::get<FeedbackPublishData>(object);

            UNIT_ASSERT_EQUAL(etalonData.entrance.name, objectData.entrance.name);
            const auto& etalonPoint = etalonData.entrance.pointGeo;
            const auto& objectPoint = objectData.entrance.pointGeo;
            UNIT_ASSERT_DOUBLES_EQUAL(etalonPoint.x(), objectPoint.x(), COMPARE_MERC_EPSILON);
            UNIT_ASSERT_DOUBLES_EQUAL(etalonPoint.y(), objectPoint.y(), COMPARE_MERC_EPSILON);

            UNIT_ASSERT_EQUAL(etalonData.reason, objectData.reason);
        } else if (std::holds_alternative<CommitPublishData>(etalon)) {
            UNIT_ASSERT(std::holds_alternative<CommitPublishData>(object));
            const CommitPublishData& etalonData = std::get<CommitPublishData>(etalon);
            const CommitPublishData& objectData = std::get<CommitPublishData>(object);

            UNIT_ASSERT_EQUAL(etalonData.entranceName, objectData.entranceName);

            if (std::holds_alternative<geolib3::Point2>(etalonData.entrance)) {
                UNIT_ASSERT(std::holds_alternative<geolib3::Point2>(objectData.entrance));
                const geolib3::Point2& etalonPoint = std::get<geolib3::Point2>(etalonData.entrance);
                const geolib3::Point2& objectPoint = std::get<geolib3::Point2>(objectData.entrance);

                UNIT_ASSERT_DOUBLES_EQUAL(etalonPoint.x(), objectPoint.x(), COMPARE_MERC_EPSILON);
                UNIT_ASSERT_DOUBLES_EQUAL(etalonPoint.y(), objectPoint.y(), COMPARE_MERC_EPSILON);
            } else if (std::holds_alternative<revision::DBID>(etalonData.entrance)) {
                UNIT_ASSERT_EQUAL(
                    std::get<revision::DBID>(etalonData.entrance),
                    std::get<revision::DBID>(objectData.entrance));
            } else {
                UNIT_ASSERT_C(false, "unknown CommitPublishData.entrance variant state");
            }
        } else if (std::holds_alternative<SkipReason>(etalon)) {
            UNIT_ASSERT(std::holds_alternative<SkipReason>(object));

            UNIT_ASSERT_EQUAL(
                std::get<SkipReason>(etalon),
                std::get<SkipReason>(object));
        } else {
            UNIT_ASSERT_C(false, "unknown PublishData variant state");
        }
    } catch (const NUnitTest::TAssertException& ) {
        INFO() << "etalon: '" << toString(etalon) << "'";
        INFO() << "object: '" << toString(object) << "'";
        throw;
    }
}

}

Y_UNIT_TEST_SUITE(entrances) {

Y_UNIT_TEST_F(test_loading, DBFixture)
{
    auto commitIds = importDataToRevision(pool(), arcadiaDataPath("buildings00.json"));
    syncViewWithRevision(*this);
    auto txn = pool().slaveTransaction();
    txn->exec("SET search_path=vrevisions_trunk,public");

    // out of bld 1
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.0003, 50.0002), "1"});
        comparePublishData(CommitPublishData{
            geolib3::convertGeodeticToMercator(geolib3::Point2(50.0003, 50.0001)),
            "1"
        }, res);
    }

    // out of bld 2 ; empty name
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.0003, 50.0008), ""});
        comparePublishData(CommitPublishData{
            geolib3::convertGeodeticToMercator(geolib3::Point2(50.0003, 50.0010)),
            ""
        }, res);
    }

    // inside bld 1
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.0007, 50.00002), "1"});
        comparePublishData(CommitPublishData{
            geolib3::convertGeodeticToMercator(geolib3::Point2(50.0007, 50.0000)),
            "1"
        }, res);
    }

    // too far from any building
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.0001, 50.00055), "3"});
        comparePublishData(FeedbackPublishData{
            Entrance{geolib3::Point2(50.0001, 50.00055), "3"},
            FeedbackReason::NoBuildingFound
        }, res);
    }

    // suspicious name
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.0001, 50.00055), "3A"});
        comparePublishData(FeedbackPublishData{
            Entrance{geolib3::Point2(50.0001, 50.00055), "3A"},
            FeedbackReason::SuspiciousName
        }, res);
    }

    // suspicious name 2
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.0001, 50.00055), "20"});
        comparePublishData(FeedbackPublishData{
            Entrance{geolib3::Point2(50.0001, 50.00055), "20"},
            FeedbackReason::SuspiciousName
        }, res);
    }

    // duplicate in building
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.0021, 50.0002), "2"});
        comparePublishData(FeedbackPublishData{
            Entrance{geolib3::Point2(50.0021, 50.0002), "2"},
            FeedbackReason::DuplicateName
        }, res);
    }

    // just set name
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.00252, 50.0002), "1"});
        comparePublishData(CommitPublishData{(revision::DBID)30, "1"}, res);
    }

    // too close to another entrance
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.00298, 50.0002), "3"});
        comparePublishData(FeedbackPublishData{
            Entrance{geolib3::Point2(50.00298, 50.0002), "3"},
            FeedbackReason::NameConflict
        }, res);
    }

    // the same one => skip
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.00298, 50.0002), "2"});
        comparePublishData(SkipReason::Duplicate, res);
    }

    // not too close to another entrance
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.0031, 50.0002), "3"});
        comparePublishData(CommitPublishData{
            geolib3::convertGeodeticToMercator(geolib3::Point2(50.0031, 50.0001)),
            "3"
        }, res);
    }

    // unnamed entrance
    {
        auto res = processEntrance(*txn, *txn,
            Entrance{geolib3::Point2(50.0034, 50.0002), ""});
        comparePublishData(CommitPublishData{
            geolib3::convertGeodeticToMercator(geolib3::Point2(50.0034, 50.0001)),
            ""
        }, res);
    }

}

} // Y_UNIT_TEST_SUITE

} // namespace maps::wiki::sprav_feedback::tests

