#include "utils.h"
#include "i18n.h"
#include "globals.h"

#include <maps/libs/sql_chemistry/include/exceptions.h>
#include <maps/libs/log8/include/log8.h>

#include <yandex/maps/i18n.h>
#include <yandex/maps/proto/mrc/common/error.sproto.h>
#include <yandex/maps/proto/offline_recording/record.pb.h>
#include <yandex/maps/pb_stream2/reader.h>
#include <maps/infra/yacare/include/params/tvm.h>

#include <library/cpp/geobase/lookup.hpp>
#include <library/cpp/digest/crc32c/crc32c.h>

namespace maps::mrc::agent_proxy {

namespace {

using CountryId = NGeobase::NImpl::Id;

std::optional<CountryId> tryEvalCountryByIp(std::string_view clientIp)
{
    try {
        const NGeobase::TLookup& geobase = Globals::geobaseLookup();
        NGeobase::TRegion region = geobase.GetRegionByIp(std::string(clientIp));
        while (region.GetEType() > NGeobase::ERegionType::COUNTRY && region.GetParentId() != NGeobase::ROOT_ID) {
            region = geobase.GetRegionById(region.GetParentId());
        }
        if (region.GetEType() == NGeobase::ERegionType::COUNTRY) {
            return std::optional<CountryId>(region.GetId());
        }
    } catch (const std::runtime_error& err) {
        // Geobase throws std::runtime_error for unknown regions
    }
    return std::nullopt;
}

bool userHasBoundPhone(const yacare::Request& request) {
    auto userInfo =
        yacare::tvm::fetchUserInfoForRequest(request,
            auth::BlackboxQueryParams().requestBoundPhones()
        );
    return !userInfo.phones().empty();
}

} // namespace

void errorReporter(const yacare::Request&, yacare::Response& resp)
{
    try {
        throw;
    } catch (const sql_chemistry::ObjectNotFound& e) {
        resp << (yacare::errors::NotFound() << e.what());
    } catch (const sql_chemistry::EditConflict& e) {
        WARN() << "Detected edit conflict: " << e.what();
        resp << (yacare::errors::Conflict() << e.what());
    } catch (const yacare::Error& e) {
        resp << e;
    } catch (const maps::Exception& e) {
        resp.setStatus(yacare::HTTPStatus::InternalError);
        resp << e.what();
        ERROR() << e;
    } catch (const std::exception& e) {
        resp.setStatus(yacare::HTTPStatus::InternalError);
        resp << e.what();
        ERROR() << "Internal error: " << e.what();
    } catch (...) {
        resp.setStatus(yacare::HTTPStatus::InternalError);
        resp << "unknown error";
    }
}

http::ETag makeEtag(size_t hash)
{
    std::ostringstream ss;
    ss << "W/\"" << std::hex << hash << '"';
    return http::ETag(ss.str());
}

http::ETag makeEtag(const std::string& data)
{
    auto hash = Crc32c(static_cast<const void*>(data.data()), data.size());
    return makeEtag(hash);
}

const std::string& findBestTaskName(
    const db::ugc::Task& task,
    const Locale& locale
)
{
    auto bestNameIt = findBestLocaleTransform(
        task.names().begin(), task.names().end(),
        locale,
        std::mem_fn(&db::ugc::TaskNamePair::first)
    );
    REQUIRE(
        bestNameIt != task.names().end(),
        "Can't find appropriate name for task #" << task.id()
    );
    return bestNameIt->second;
}

std::pair<chrono::TimePoint, chrono::TimePoint>
readFirstAndLastRecordTimestamps(const std::string& data)
{
    namespace precord = yandex::maps::proto::offline::recording::record;

    std::istringstream stream(data);
    maps::pb_stream2::Reader reader(&stream);

    auto it = reader.begin();
    ASSERT(it != reader.end());

    uint64_t firstTs = it->as<precord::Record>().timestamp();
    uint64_t lastTs = firstTs;

    for (++it; it != reader.end(); ++it) {
        lastTs = it->as<precord::Record>().timestamp();
    }

    return {chrono::TimePoint{std::chrono::seconds(firstTs)},
            chrono::TimePoint{std::chrono::seconds(lastTs)}};
}

void checkUserCanPublishUgcContent(const yacare::Request& request)
{
    static constexpr CountryId BELARUS_COUNTRY_ID = 149;
    std::optional<CountryId> countryId = tryEvalCountryByIp(request.getClientIpAddress());
    if (!countryId.has_value()) {
        return;
    }
    if (countryId.value() == BELARUS_COUNTRY_ID && !userHasBoundPhone(request)) {
        yandex::maps::sproto::mrc::common::Error error;
        error.code() = yandex::maps::sproto::mrc::common::Error::Code::LEGAL_REASON_PHONE_REQUIRED;
        std::locale locale = yacare::response().getloc();
        error.description() = localizeString(I18nToken::BelarusLawRequireBoundPhone, locale);
        throw YacareProtobufError(yacare::Status::LegalReasons, error);
    }
}

} //maps::mrc::agent_proxy

