#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>

#include <maps/wikimap/mapspro/services/mrc/eye/lib/location/include/rotation.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature_gateway.h>

#include <maps/libs/common/include/exception.h>
#include <maps/libs/geolib/include/direction.h>
#include <maps/libs/geolib/include/units_literals.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/sql_chemistry/include/batch_load.h>

#include <sstream>


namespace maps::mrc::eye {

using namespace geolib3::literals;

auto filter()
{
    return db::table::Feature::cameraOrientationRodrigues.isNotNull()
            && db::table::Feature::orientation.isNotNull()
            && db::table::Feature::heading.isNotNull()
            && db::table::Feature::cameraDeviation.isNotNull()
            && db::table::Feature::isPublished;
}

db::CameraDeviation findCameraDeviation(geolib3::Heading move, geolib3::Heading camera)
{
    const geolib3::Degrees angle = geolib3::orientedAngleBetween(move, camera);

    db::CameraDeviation result;
    if (-135_deg < angle && angle < -45_deg) {
        result = db::CameraDeviation::Left;
    } else if (-45_deg <= angle && angle <= 45_deg) {
        result = db::CameraDeviation::Front;
    } else if (45_deg < angle && angle < 135_deg) {
        result = db::CameraDeviation::Right;
    } else {
        result = db::CameraDeviation::Back;
    }

    return result;
}

void run(const common::Config& cfg, std::ostream& out)
{
    INFO() << "Starting...";

    wiki::common::PoolHolder mrc(cfg.makePoolHolder());
    auto txn = mrc.pool().slaveTransaction();

    size_t count = 0;
    for (sql_chemistry::BatchLoad<db::table::Feature> batch(10000, filter()); batch.next(*txn); ) {
        count += batch.result().size();
        INFO() << "Load features " << count;

        for (const auto& feature: batch) {
            const Eigen::Quaterniond rotation = toRotation(feature.cameraRodrigues());
            const auto [heading, orientation, pitch] = decomposeRotation(rotation);

            if (tagged::abs(pitch) >= geolib3::Degrees(60)) {
                out << "pitch " << pitch << " " << feature.id() << std::endl;
                continue;
            }

            if (orientation != feature.orientation()) {
                out << "orientation " << int(feature.orientation()) << " "
                    << int(orientation) << " " << feature.id() << std::endl;
            }

            const auto cameraDeviation = findCameraDeviation(feature.heading(), heading);
            if (cameraDeviation != feature.cameraDeviation()) {
                out << "deviation " << db::toIntegral(feature.cameraDeviation()) << " "
                    << db::toIntegral(cameraDeviation) << " "
                    << feature.id() << std::endl;

                out << feature.heading() << " " << heading << std::endl;
            }
        }

        out.flush();
    }
}

} // namespace maps::mrc::eye

int main(int argc, const char** argv) try {
    maps::cmdline::Parser parser(
        "Test consistency of mrc feature fields: (orientation, camera_deviation) vs camera_rodrigues"
    );

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

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

    auto outPath = parser.string("out")
            .required()
            .help("path to result file");

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

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

    std::ofstream out(outPath);
    maps::mrc::eye::run(config, out);

    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!";
}
