#include <maps/wikimap/mapspro/services/tasks_realtime/src/mrc_pedestrian_regions_logger/lib/log_mrc_pedestrian_regions.h>

#include <maps/wikimap/mapspro/libs/revision/ut/fixtures/revision_creator.h>
#include <yandex/maps/wiki/unittest/arcadia.h>

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

#include <vector>


namespace maps::wiki::tasks::log_mrc_pedestrian_regions::test {

using revision::tests::RevisionCreator;
using revision::tests::makeRelation;
using testing::_;


class YtTableWriterMock final: public IYtTableWriter {
public:
    MOCK_METHOD(void, addRow, (const NYT::TNode& row), (override));
    MOCK_METHOD(void, finish, (), (override));
};


class DbFixture: public unittest::ArcadiaDbFixture {
public:
    DbFixture()
        : readTxn(pool().slaveTransaction())
        , writeTxn(pool().masterWriteableTransaction())
    {}

protected:
    pgpool3::TransactionHandle readTxn;
    pgpool3::TransactionHandle writeTxn;
    YtTableWriterMock ytTableWriter;
};


template<typename T>
bool nodeMapContains(
    const NYT::TNode::TMapType& nodeMap,
    std::string_view key,
    const std::optional<T>& expValue,
    testing::MatchResultListener* result_listener)
{
    if (!nodeMap.contains(key)) {
        *result_listener << "log entry has no '" << key << "' key";
        return false;
    }

    if (!expValue) {
        if (nodeMap.at(key).HasValue()) {
            *result_listener << "unexpected log entry at '" << key << "'";
            return false;
        } else {
            return true;
        }
    }

    if (!nodeMap.at(key).HasValue()) {
        *result_listener << "log entry at '" << key << "' has no value";
        return false;
    }
    const auto gotValue = nodeMap.at(key).template As<T>();
    if (gotValue != *expValue) {
        *result_listener << "wrong " << key << ": '" << gotValue << "' != '" << *expValue << "'";
        return false;
    }
    return true;
}


MATCHER_P10(
    LogEntry,
    expObjectId, expCompanyId, expName, expStatus, expIsIndoor, expIsPanoramic, expIsTesting, expUid, expLogin, expAd,
    "")
{
    if (!arg.HasValue()) {
        *result_listener << "log entry has no value";
        return false;
    }

    if (!arg.IsMap()) {
        *result_listener << "log entry is not map";
        return false;
    }
    const auto nodeMap = arg.AsMap();

    bool result = true;
    result &= nodeMapContains<ui64>   (nodeMap, "object_id",        expObjectId,    result_listener);
    result &= nodeMapContains<TString>(nodeMap, "company_id",       expCompanyId,   result_listener);
    result &= nodeMapContains<TString>(nodeMap, "name",             expName,        result_listener);
    result &= nodeMapContains<TString>(nodeMap, "status",           expStatus,      result_listener);
    result &= nodeMapContains<bool>   (nodeMap, "is_indoor",        expIsIndoor,    result_listener);
    result &= nodeMapContains<bool>   (nodeMap, "is_panoramic",     expIsPanoramic, result_listener);
    result &= nodeMapContains<bool>   (nodeMap, "is_testing",       expIsTesting,   result_listener);
    result &= nodeMapContains<i64>    (nodeMap, "pedestrian_uid",   expUid,         result_listener);
    result &= nodeMapContains<TString>(nodeMap, "pedestrian_login", expLogin,       result_listener);
    result &= nodeMapContains<ui64>   (nodeMap, "ad_id",            expAd,          result_listener);
    return result;
}


const auto NO_COMPANY_ID   = std::nullopt;
const auto NO_NAME         = std::nullopt;
const auto NO_IS_INDOOR    = std::nullopt;
const auto NO_IS_PANORAMIC = std::nullopt;
const auto NO_IS_TESTING   = std::nullopt;
const auto NO_UID          = std::nullopt;
const auto NO_LOGIN        = std::nullopt;
const auto NO_AD           = std::nullopt;

const auto DEFAULT_STATUS = "draft";


Y_UNIT_TEST_SUITE_F(mrc_pedestrian_region_logger, DbFixture)
{
    Y_UNIT_TEST(shouldNotLogIfCommitIdsAbsent)
    {
        EXPECT_CALL(ytTableWriter, addRow(_)).Times(0);
        logMrcPedestrianRegions(*readTxn, ytTableWriter, {});
    }

    Y_UNIT_TEST(shouldLogMrcPedestrianRegionsOnly)
    {
        const std::vector revisionIds = {
            RevisionCreator(*writeTxn)
                .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:status", "status 1"}})
                .geometry("POINT(1 1)")(),
            RevisionCreator(*writeTxn)
                .objectAttrs({{"cat:other", "1"}, {"mrc_pedestrian_region:status", "status 2"}})
                .geometry("POINT(2 2)")(),
            RevisionCreator(*writeTxn)
                .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:status", "status 3"}})
                .geometry("POINT(3 3)")(),
            };

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(revisionIds[0].objectId(), NO_COMPANY_ID, NO_NAME, "status 1", NO_IS_INDOOR, NO_IS_PANORAMIC, NO_IS_TESTING, NO_UID, NO_LOGIN, NO_AD)));
        EXPECT_CALL(ytTableWriter, addRow(LogEntry(revisionIds[2].objectId(), NO_COMPANY_ID, NO_NAME, "status 3", NO_IS_INDOOR, NO_IS_PANORAMIC, NO_IS_TESTING, NO_UID, NO_LOGIN, NO_AD)));
        logMrcPedestrianRegions(
            *writeTxn, ytTableWriter,
            {revisionIds[0].commitId(), revisionIds[1].commitId(), revisionIds[2].commitId()}
        );
    }

    Y_UNIT_TEST(shouldNotLogMrcPedestrianRegionsWithoutGeometry)
    {
        const auto revisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:mrc_pedestrian_region", "1"}})();

        EXPECT_CALL(ytTableWriter, addRow(_)).Times(0);
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {revisionId.commitId()});
    }

    Y_UNIT_TEST(shouldLogDefaultStatusIfStatusAbsent)
    {
        const auto revisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:mrc_pedestrian_region", "1"}})
            .geometry("POINT(1 1)")();

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(revisionId.objectId(), NO_COMPANY_ID, NO_NAME, DEFAULT_STATUS, NO_IS_INDOOR, NO_IS_PANORAMIC, NO_IS_TESTING, NO_UID, NO_LOGIN, NO_AD)));
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {revisionId.commitId()});
    }

    Y_UNIT_TEST(shouldLogMrcPedestrianRegionsCompanyId)
    {
        const auto revisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:company_id", "company id"}})
            .geometry("POINT(1 1)")();

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(revisionId.objectId(), "company id", NO_NAME, DEFAULT_STATUS, NO_IS_INDOOR, NO_IS_PANORAMIC, NO_IS_TESTING, NO_UID, NO_LOGIN, NO_AD)));
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {revisionId.commitId()});
    }

    Y_UNIT_TEST(shouldLogMrcPedestrianRegionsName)
    {
        const auto revisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:name", "name"}})
            .geometry("POINT(1 1)")();

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(revisionId.objectId(), NO_COMPANY_ID, "name", DEFAULT_STATUS, NO_IS_INDOOR, NO_IS_PANORAMIC, NO_IS_TESTING, NO_UID, NO_LOGIN, NO_AD)));
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {revisionId.commitId()});
    }

    Y_UNIT_TEST(shouldLogIsIndoor)
    {
        const auto revisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:is_indoor", "1"}})
            .geometry("POINT(1 1)")();

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(revisionId.objectId(), NO_COMPANY_ID, NO_NAME, DEFAULT_STATUS, true, NO_IS_PANORAMIC, NO_IS_TESTING, NO_UID, NO_LOGIN, NO_AD)));
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {revisionId.commitId()});
    }

    Y_UNIT_TEST(shouldLogIsPanoramic)
    {
        const auto revisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:is_panoramic", "1"}})
            .geometry("POINT(1 1)")();

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(revisionId.objectId(), NO_COMPANY_ID, NO_NAME, DEFAULT_STATUS, NO_IS_INDOOR, true, NO_IS_TESTING, NO_UID, NO_LOGIN, NO_AD)));
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {revisionId.commitId()});
    }

    Y_UNIT_TEST(shouldLogIsTesting)
    {
        const auto revisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:is_testing", "1"}})
            .geometry("POINT(1 1)")();

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(revisionId.objectId(), NO_COMPANY_ID, NO_NAME, DEFAULT_STATUS, NO_IS_INDOOR, NO_IS_PANORAMIC, true, NO_UID, NO_LOGIN, NO_AD)));
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {revisionId.commitId()});
    }

    Y_UNIT_TEST(shouldLogPedestrianLoginAndUid)
    {
        const auto revisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:pedestrian_login_uid", "111"}, {"mrc_pedestrian_region:pedestrian_login", "login"}})
            .geometry("POINT(1 1)")();

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(revisionId.objectId(), NO_COMPANY_ID, NO_NAME, "draft", NO_IS_INDOOR, NO_IS_PANORAMIC, NO_IS_TESTING, 111, "login", NO_AD)));
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {revisionId.commitId()});
    }

    Y_UNIT_TEST(shouldLogOnlyPassedCommitIds)
    {
        const std::vector revisionIds = {
            RevisionCreator(*writeTxn)
                .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:company_id", "company 1"}, {"mrc_pedestrian_region:status", "status 1"}})
                .geometry("POINT(1 1)")(),
            RevisionCreator(*writeTxn)
                .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:company_id", "company 2"}, {"mrc_pedestrian_region:status", "status 2"}})
                .geometry("POINT(2 2)")(),
            };

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(revisionIds[1].objectId(), "company 2", NO_NAME, "status 2", NO_IS_INDOOR, NO_IS_PANORAMIC, NO_IS_TESTING, NO_UID, NO_LOGIN, NO_AD)));
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {revisionIds[1].commitId()});
    }

    Y_UNIT_TEST(shouldLogRelation)
    {
        const std::vector regionRevisionIds = {
            RevisionCreator(*writeTxn)
                .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:status", "status 1"}})
                .geometry("POINT(1 1)")(),
            RevisionCreator(*writeTxn)
                .objectAttrs({{"cat:mrc_pedestrian_region", "1"}, {"mrc_pedestrian_region:status", "status 2"}})
                .geometry("POINT(2 2)")(),
        };
        const auto adRevisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:ad", "1"}})();
        const auto relationRevisionId =
            makeRelation(*writeTxn, "mrc_pedestrian_region_associated_with", adRevisionId.objectId(), regionRevisionIds[1].objectId());

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(regionRevisionIds[1].objectId(), NO_COMPANY_ID, NO_NAME, "status 2", NO_IS_INDOOR, NO_IS_PANORAMIC, NO_IS_TESTING, NO_UID, NO_LOGIN, adRevisionId.objectId())));
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {relationRevisionId.commitId()});
    }

    Y_UNIT_TEST(shouldLogRelationRemoval)
    {
        const auto regionRevisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:mrc_pedestrian_region", "1"}})
            .geometry("POINT(1 1)")();
        const auto adRevisionId = RevisionCreator(*writeTxn)
            .objectAttrs({{"cat:ad", "1"}})();

        const auto relationRevisionId =
            makeRelation(*writeTxn, "mrc_pedestrian_region_associated_with", adRevisionId.objectId(), regionRevisionId.objectId());
        writeTxn->exec(
            "UPDATE revision.object_revision "
            "SET deleted = TRUE "
            "WHERE commit_id = " + std::to_string(relationRevisionId.commitId())
        );

        EXPECT_CALL(ytTableWriter, addRow(LogEntry(regionRevisionId.objectId(), NO_COMPANY_ID, NO_NAME, DEFAULT_STATUS, NO_IS_INDOOR, NO_IS_PANORAMIC, NO_IS_TESTING, NO_UID, NO_LOGIN, NO_AD)));
        logMrcPedestrianRegions(*writeTxn, ytTableWriter, {relationRevisionId.commitId()});
    }
} // Y_UNIT_TEST_SUITE

} // maps::wiki::tasks::log_mrc_pedestrian_regions::test
