#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/signdetect/include/signdetect_faster_rcnn.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/types.h>
#include <yandex/maps/mrc/traffic_signs/signs.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/exif.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/vault_boy/include/secrets.h>
#include <maps/libs/common/include/retry.h>
#include <maps/libs/http/include/http.h>
#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/shell_cmd.h>

#include <string>

using namespace std::string_literals;

namespace {
maps::vault_boy::MemoryContext
loadValtContext(const std::string& secretVersion)
{
    auto yaCmdResult = maps::shell::runCmd("ya vault get version "s + secretVersion
        + " -j"s);
    REQUIRE(!yaCmdResult.exitCode, "failed for load secrets with version"
        << secretVersion);
    auto secretsJson = maps::json::Value::fromString(yaCmdResult.stdOut);
    auto secrets = secretsJson["value"].as<std::unordered_map<std::string, std::string>>();
    maps::vault_boy::MemoryContext result;
    for(auto [key, value] : secrets) {
        result.add(key, value);
    }
    return result;
}

maps::mrc::common::Bytes load(const std::string& url)
{
    INFO() << "Loading image by url " << url;
    static constexpr int HTTP_STATUS_OK = 200;

    maps::http::Client client;
    client.setTimeout(std::chrono::seconds(5));

    return retry(
        [&]() -> maps::mrc::common::Bytes {
            maps::http::Request request(client, maps::http::GET, maps::http::URL(url));
            auto response = request.perform();

            if (response.status() != HTTP_STATUS_OK) {
                throw maps::RuntimeError() << "Http status " << response.status();
            }

            return response.readBodyToVector();
        },
        ::maps::common::RetryPolicy().setTryNumber(5)
    );
}

} // namespace

int main(int argc, const char** argv) try {
    using namespace maps;
    log8::setLevel(log8::Level::INFO);
    cmdline::Parser parser;

    auto configPath = parser.string("config")
            .help("path to mrc config")
            .required();

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

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

    INFO() << "Loading config";
    const mrc::common::Config config(loadValtContext(secretVersion), configPath);
    auto pgpool = config.makePoolHolder();
    auto mdsClient = config.makeMdsClient();

    INFO() << "Loading detector";
    mrc::signdetect::FasterRCNNDetector signsDetector;

    for (auto& featureIdStr : parser.argv()) {
        INFO() << "Processing featureId: " << featureIdStr;
        auto featureId = std::stoll(featureIdStr);
        auto feature = mrc::db::FeatureGateway(*pgpool.pool().slaveTransaction())
            .loadById(featureId);

        auto url = mdsClient.makeReadUrl(feature.mdsKey());
        auto imageBytes = load(url);
        cv::Mat image = maps::mrc::common::decodeImage(imageBytes);
        image = transformByImageOrientation(image, feature.orientation());

        for (auto sign : signsDetector.detect(image)) {
            INFO() << sign;
        }
    }

    return EXIT_SUCCESS;
} catch (const maps::Exception& e) {
    ERROR() << e;
    return EXIT_FAILURE;
} catch (const std::exception& e) {
    ERROR() << e.what();
    return EXIT_FAILURE;
} catch (...) {
    ERROR() << "Caught unknown exception";
    return EXIT_FAILURE;
}

