#include <maps/libs/common/include/exception.h>

#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/monitoring/include/check.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/yt/common/include/utils.h>

#include <maps/libs/common/include/file_utils.h>

#include <mapreduce/yt/interface/client.h>

#include <regex>
#include <fstream>
#include <optional>
#include <utility>

using namespace maps::mrc::eye;

constexpr const char* DATE = "date";
constexpr const char* QUEUE_SIZE = "size";

using Date = TString;

Date getMoscowDate() {
    static constexpr const char* DATE_FORMAT = "%Y-%m-%d %H:%M:%S";
    const maps::chrono::TimePoint time = maps::chrono::TimePoint::clock::now();
    const maps::chrono::TimePoint moscowTime = time + std::chrono::hours(3);
    return maps::chrono::formatIntegralDateTime(moscowTime, DATE_FORMAT).c_str();
}

class QueueTable {
public:
    static QueueTable createIfNotExists(
        NYT::IClientPtr client,
        const std::string& tableName)
    {
        if (!client->Exists(NYT::TYPath(tableName))) {
            client->Create(NYT::TYPath(tableName), NYT::NT_TABLE,
                NYT::TCreateOptions()
                    .Recursive(true)
                    .Attributes(NYT::TNode()("schema", queueTableSchema()))
            );
        }

        return QueueTable{client, tableName};
    }

    bool isUpToDate(const Date& currentDate) {
        std::optional<Date> lastDate = getLastDate();
        return lastDate.has_value()
            && lastDate.value() >= currentDate;
    }

    void addQueue(const Date& date, size_t queueSize) {
        NYT::TNode node = NYT::TNode()
            (DATE, date)
            (QUEUE_SIZE, queueSize);

        auto writer = client_->CreateTableWriter<NYT::TNode>(
            NYT::TRichYPath(tableName_).Append(true)
        );
        writer->AddRow(node);
        writer->Finish();
    }

private:
    QueueTable(NYT::IClientPtr client, const std::string& tableName)
        : client_(client)
        , tableName_(tableName)
    {}

    static NYT::TNode queueTableSchema() {
        return NYT::TTableSchema()
            .AddColumn(DATE, NYT::EValueType::VT_STRING, NYT::ESortOrder::SO_ASCENDING)
            .AddColumn(QUEUE_SIZE, NYT::EValueType::VT_UINT64).ToNode();
    }

    std::optional<Date> getLastDate() {
        auto row = maps::yt::getLastRecordFromTable<NYT::TNode>(client_, tableName_);
        if (!row.Defined()) {
            return std::nullopt;
        }
        return (*row)[DATE].AsString();
    }

    NYT::IClientPtr client_;
    NYT::TYPath tableName_;
};

int main(int argc, const char** argv) try {
    NYT::Initialize(argc, argv);

    maps::cmdline::Parser parser("Add quality to table");

    auto mrcConfigPath = parser.string("mrc-config")
            .help("path to mrc config");

    auto secretVersion = parser.string("secret-version")
            .help("version for secrets from yav.yandex-team.ru");

    auto gatewayName = parser.string("gateway")
        .help("queue gateway")
        .required();

    auto metadataName = parser.string("metadata")
        .help("service metadata key");

    auto txnIdKey = parser.string("txn-id-key")
        .help("txn id key in targets.metadata table");

    auto tableName = parser.string("table-name")
        .required()
        .help("Path to YT table");

    parser.parse(argc, const_cast<char**>(argv));

    REQUIRE(metadataName.defined() && !txnIdKey.defined() ||
            !metadataName.defined() && txnIdKey.defined(),
            "Should specified only 'metadata' or 'txn-id-key'");

    const auto config = maps::mrc::common::templateConfigFromCmdPath(secretVersion, mrcConfigPath);

    QueueCheckerPtr checker = metadataName.defined() ?
          QueueChecker::byMetadataName(gatewayName, metadataName)
        : QueueChecker::byTxnIdKey(gatewayName, txnIdKey);

    auto pool = config.makePoolHolder();
    auto txn = pool.pool().slaveTransaction();

    size_t queueSize = checker->getQueueSize(*txn);

    QueueTable table = QueueTable::createIfNotExists(
        NYT::CreateClient("hahn"), tableName
    );

    Date currentDate = getMoscowDate();

    if (!table.isUpToDate(currentDate)) {
        table.addQueue(currentDate, queueSize);
    } else {
        INFO() << "Queue table is up to date";
    }

    return EXIT_SUCCESS;
} catch (const maps::Exception& ex) {
    FATAL() << "Failed: " << ex;
    return EXIT_FAILURE;
} catch (const std::exception& ex) {
    FATAL() << "Failed: " << ex.what();
    return EXIT_FAILURE;
} catch (...) {
    FATAL() << "Unknown error!";
}
