#include "yt_row.h"

#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>
#include <util/string/cast.h>
#include <yandex/maps/wiki/common/string_utils.h>
#include <optional>
#include <vector>

namespace maps::wiki::toloka_downloader {

namespace {

geolib3::Point2 reversedCoordsToPoint(const std::string& str)
{
    auto latlon = common::split(str, ",");
    REQUIRE(latlon.size() == 2, "Point string " + str + " cannot be parsed");

    return geolib3::Point2(std::stod(latlon[1]), std::stod(latlon[0]));
}

std::vector<std::string> photoUrlsFromNode(const NYT::TNode& node)
{
    if (node.IsNull() || node.IsUndefined()) {
        return {};
    }

    REQUIRE(node.IsList(), "Photos is not list");

    std::vector<std::string> photoUrls;
    for (const NYT::TNode& photoUrlNode : node.AsList()) {
        photoUrls.emplace_back(photoUrlNode.AsString());
    }

    return photoUrls;
}

std::optional<std::string> optionalStringFromNode(const NYT::TNode& node)
{
    std::optional<std::string> retVal;
    if (!node.IsNull()) {
        auto value = node.AsString();
        if (!value.empty()) {
            retVal = value;
        }
    }
    return retVal;
}

std::optional<std::string> loadComment(const NYT::TNode& column)
{
    auto comment = optionalStringFromNode(column);
    if (comment) {
        INFO() << "comment: " << *comment;
    }
    return comment;
}

std::optional<std::string> loadEntranceId(const NYT::TNode& column)
{
    auto entranceId = optionalStringFromNode(column);
    if (entranceId) {
        INFO() << "entranceId: " << *entranceId;
    }
    return entranceId;
}

chrono::TimePoint loadSubmitTs(const NYT::TNode& column)
{
    const auto submitTsNumeric = column.IntCast<uint64_t>();
    auto submitTs =
        chrono::sinceEpochToTimePoint<std::chrono::milliseconds>(submitTsNumeric);
    INFO() << "submitTs: " << submitTsNumeric << " " << chrono::formatIsoDateTime(submitTs);
    return submitTs;
}

Assignment::PhotoUrls loadPhotos(const NYT::TNode& column)
{
    auto photos = photoUrlsFromNode(column);
    REQUIRE(!photos.empty(), "Photos can not be empty");

    Assignment::PhotoUrls photoUrls;
    for (const auto& photo : photos) {
        INFO() << "photo url: " << photo;
        REQUIRE(photoUrls.emplace(photo).second,
                "Duplicated photo: " << photo);
    }
    return photoUrls;
}

Assignment::Data parseAssignmentData(const NYT::TNode& row)
{
    auto loadPosition = [&](const auto& columnName) {
        auto point = reversedCoordsToPoint(row[columnName].AsString());
        INFO() << columnName << ": lon=" << std::to_string(point.x()) << " lat=" << std::to_string(point.y());
        return point;
    };

    return Assignment::Data{
        .submitTs = loadSubmitTs(row["submitTs"]),
        .photoUrls = loadPhotos(row["photos"]),
        .objectPosition = loadPosition("coordinates"),
        .workerPosition = loadPosition("photoPoint"),
        .comment = loadComment(row["comment"]),
        .nmapsObjectId = loadEntranceId(row["entrance_id"]),
    };
}

} // namespace

std::optional<Assignment> YtRow::createAssignment() const
{
    PoolId poolId = 0;
    std::string assignmentId;

    try {
        const auto& poolIdNode = row_["poolId"];
        REQUIRE(poolIdNode.IsString(),
                "poolId not String, type: " << poolIdNode.GetType());

        auto poolIdStr = poolIdNode.AsString();
        REQUIRE(TryFromString(poolIdStr, poolId),
                "invalid poolId: " << poolIdStr);

        const auto& assignmentIdNode = row_["assignmentId"];
        REQUIRE(poolIdNode.IsString(),
                "assignmentId not String, type: " << assignmentIdNode.GetType());

        assignmentId = assignmentIdNode.AsString();
        REQUIRE(!assignmentId.empty(), "empty assignmentId");
        return Assignment{poolId, assignmentId};
    } catch (const std::exception& ex) {
        ERROR() << "Error on parse assignment ids,"
                << " poolId: " << poolId
                << " id: " << assignmentId << " : " << ex.what();
    }
    return std::nullopt;
}

bool YtRow::loadAssignmentData(Assignment& assignment) const
{
    try {
        assignment.setData(parseAssignmentData(row_));
        return true;
    } catch (const maps::Exception& ex) {
        ERROR() << "Error on parse assignment,"
                << " poolId: " << assignment.poolId()
                << " id: " << assignment.id() << " : " << ex;
    } catch (const std::exception& ex) {
        ERROR() << "Error on parse assignment,"
                << " poolId: " << assignment.poolId()
                << " id: " << assignment.id() << " : " << ex.what();
    }
    return false;
}

} // namespace maps::wiki::toloka_downloader
