#include <yandex/maps/wiki/configs/editor/config_holder.h>
#include <yandex/maps/wiki/pubsub/commit_consumer.h>
#include <yandex/maps/wiki/revision/common.h>
#include <yandex/maps/wiki/unittest/arcadia.h>

#include <maps/wikimap/mapspro/services/tasks_social/src/stats_updater/lib/revision_helpers.h>
#include <maps/wikimap/mapspro/services/tasks_social/src/stats_updater/lib/commits_processor.h>

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

#include <pqxx/pqxx>

#include <map>
#include <string>
#include <vector>

namespace maps::wiki::stats_updater::tests {

using namespace testing;
using revision::UserID;


Y_UNIT_TEST_SUITE_F(stats_updater_commits_processor, unittest::ArcadiaDbFixture)
{
    std::map<UserID, std::string>
    getFirstCommitAtByAuthor(pqxx::transaction_base& txn)
    {
        std::map<UserID, std::string> result;
        auto query = "SELECT uid, first_commit_at AT TIME ZONE '+0' FROM social.stats";
        for (const auto row: txn.exec(query)) {
            result[row[0].as<UserID>()] = row[1].as<std::string>("NULL");
        }
        return result;
    }

    Y_UNIT_TEST(firstCommitAt)
    {
        configs::editor::ConfigHolder configHolder(
            ArcadiaSourceRoot() + "/maps/wikimap/mapspro/cfg/editor/editor.xml");

        {   // Setup
            auto txn = pool().masterWriteableTransaction();
            txn->exec(
                "ALTER TABLE revision.commit ALTER COLUMN trunk SET DEFAULT TRUE;"
                "ALTER TABLE revision.commit ALTER COLUMN attributes SET DEFAULT 'attr => value';"
            );
            txn->commit();
        }

        {   // Should write first_commit_at for new users only
            auto txn = pool().masterWriteableTransaction();

            txn->exec(
                "INSERT INTO social.stats "
                "(uid, first_commit_at) VALUES "
                "(1, NULL);"

                "INSERT INTO revision.commit "
                "(id, created_by, created) VALUES "
                "(1, 1, '2008-08-08 08:08:08+0'), "
                "(2, 2, '2008-08-08 08:08:08+0');"
            );

            CommitsProcessor processor(*txn, configHolder.categoryGroups());
            processor.processCommits(loadCommits({1, 2}, *txn));

            const auto firstCommitAtByAuthor = getFirstCommitAtByAuthor(*txn);
            ASSERT_EQ(firstCommitAtByAuthor.size(), 2ul);
            EXPECT_EQ(firstCommitAtByAuthor.at(1), "NULL");
            EXPECT_EQ(firstCommitAtByAuthor.at(2), "2008-08-08 08:08:08");
        }

        {   // Should write first_commit_at for several users
            auto txn = pool().masterWriteableTransaction();

            txn->exec(
                "INSERT INTO revision.commit "
                "(id, created_by, created) VALUES "
                "(1, 1, '2009-09-09 09:09:09+0'), "
                "(2, 2, '2008-08-08 08:08:08+0');"
            );

            CommitsProcessor processor(*txn, configHolder.categoryGroups());
            processor.processCommits(loadCommits({1, 2}, *txn));

            const auto firstCommitAtByAuthor = getFirstCommitAtByAuthor(*txn);
            ASSERT_EQ(firstCommitAtByAuthor.size(), 2ul);
            EXPECT_EQ(firstCommitAtByAuthor.at(1), "2009-09-09 09:09:09");
            EXPECT_EQ(firstCommitAtByAuthor.at(2), "2008-08-08 08:08:08");
        }

        {   // Should write min first_commit_at
            auto txn = pool().masterWriteableTransaction();

            txn->exec(
                "INSERT INTO revision.commit "
                "(id, created_by, created) VALUES "
                "(1, 1, '2009-09-09 09:09:09+0'), "
                "(2, 1, '2007-07-07 07:07:07+0'),"
                "(3, 1, '2008-08-08 08:08:08+0');"
            );

            CommitsProcessor processor(*txn, configHolder.categoryGroups());
            processor.processCommits(loadCommits({1, 2, 3}, *txn));

            const auto firstCommitAtByAuthor = getFirstCommitAtByAuthor(*txn);
            ASSERT_EQ(firstCommitAtByAuthor.size(), 1ul);
            EXPECT_EQ(firstCommitAtByAuthor.at(1), "2007-07-07 07:07:07");
        }

        {   // Should not update first_commit_at even for an earlier commit
            // (uncommon situation)
            auto txn = pool().masterWriteableTransaction();

            txn->exec(
                "INSERT INTO social.stats "
                "(uid, first_commit_at) VALUES "
                "(1, '2008-08-08 08:08:08+0');"

                "INSERT INTO revision.commit "
                "(id, created_by, created) VALUES "
                "(1, 1, '2007-07-07 07:07:07+0');"
            );

            CommitsProcessor processor(*txn, configHolder.categoryGroups());
            processor.processCommits(loadCommits({1}, *txn));

            const auto firstCommitAtByAuthor = getFirstCommitAtByAuthor(*txn);
            ASSERT_EQ(firstCommitAtByAuthor.size(), 1ul);
            EXPECT_EQ(firstCommitAtByAuthor.at(1), "2008-08-08 08:08:08");
        }
    }
}

} // namespace maps::wiki::stats_updater::tests
