#include "exception_handling.h"

#include "serialize/formatter.h"
#include "serialize/json_writer.h"

#include <maps/wikimap/mapspro/libs/controller/include/exception.h>
#include <maps/wikimap/mapspro/libs/controller/include/profile.h>
#include <yandex/maps/wiki/configs/editor/exception.h>
#include <yandex/maps/wiki/revision/exception.h>
#include <yandex/maps/wiki/topo/exception.h>
#include <maps/libs/json/include/exception.h>
#include <maps/wikimap/mapspro/libs/acl/include/exception.h>
#include <yandex/maps/wiki/social/exception.h>

namespace maps::wiki {

namespace {

const std::map<topo::ErrorCode, std::string> TOPO_EXCEPTION_TO_ERROR_CODES =
{
    { topo::ErrorCode::Unspecified, ERR_TOPO_UNSPECIFIED },
    { topo::ErrorCode::SelfIntersection, ERR_TOPO_SELF_INTERSECTION },
    { topo::ErrorCode::SelfOverlap, ERR_TOPO_SELF_OVERLAP },
    { topo::ErrorCode::TooManyIntersectionsWithNetwork, ERR_TOPO_TOO_MANY_INTERSECTIONS_WITH_NETWORK },
    { topo::ErrorCode::TooManyIntersectionsWithElement, ERR_TOPO_TOO_MANY_INTERSECTIONS_WITH_ELEMENT },
    { topo::ErrorCode::UnexpectedIntersection, ERR_TOPO_UNEXPECTED_INTERSECTION },
    { topo::ErrorCode::DegenerateSegment, ERR_TOPO_DEGENERATE_SEGMENT },
    { topo::ErrorCode::TooLongSegment, ERR_TOPO_TOO_LONG_SEGMENT },
    { topo::ErrorCode::DegenerateEdge, ERR_TOPO_DEGENERATE_EDGE },
    { topo::ErrorCode::TooLongEdge, ERR_TOPO_TOO_LONG_EDGE },
    { topo::ErrorCode::AmbiguousSnapping, ERR_TOPO_AMBIGUOUS_SNAPPING },
    { topo::ErrorCode::InvalidRestrictions, ERR_TOPO_INVALID_RESTRICTIONS },
    { topo::ErrorCode::NodeDeletionForbidden, ERR_TOPO_NODE_DELETION_FORBIDDEN },
    { topo::ErrorCode::MergeOfOverlappedEdgesForbidden, ERR_TOPO_MERGE_OF_OVERLAPPED_EDGES_FORBIDDEN },
    { topo::ErrorCode::Unsupported, ERR_TOPO_UNSUPPORTED_OPERATION }
};

controller::ExceptionInfo
formatException(
    const maps::Exception& ex,
    bool withBacktrace,
    common::FormatType formatType,
    IsInternalError isInternalError)
{
    auto formatter = Formatter::create(formatType);
    return {
        ex.what(),
        formatter->formatException(ex, withBacktrace),
        isInternalError
    };
}

std::string
collectMessages(const maps::Exception& ex)
{
    std::string message;
    for (auto exPtr = &ex; exPtr; exPtr = exPtr->cause()) {
        const char* whatStr = exPtr->what();
        if (*whatStr) {
            message = message.empty() ? whatStr : message + " <- " + whatStr;
        }
    }
    return message;
}

controller::ExceptionInfo
handleInternalError(
    maps::Exception& ex,
    const std::string& callDetails,
    common::FormatType formatType,
    InternalErrorPolicy internalErrorPolicy,
    Profile* profile)
{
    ex.attrs().insert(std::make_pair("status", INTERNAL_ERROR));

    auto message = collectMessages(ex);
    if (profile) {
        profile->error(message);
    } else {
        ERROR() << message << " DETAILS:" << callDetails;
    }
    if (internalErrorPolicy == InternalErrorPolicy::Throw) {
        throw InternalErrorException();
    }
    return formatException(ex, true, formatType, IsInternalError::Yes);
}

controller::ExceptionInfo
handleLogicError(
    const maps::wiki::LogicException& ex,
    const std::string& callDetails,
    common::FormatType formatType,
    Profile* profile)
{
    auto message = collectMessages(ex);
    if (profile) {
        profile->warning(message);
    } else {
        WARN() << message << " DETAILS:" << callDetails;
    }
    return formatException(ex, false, formatType, IsInternalError::No);
}

controller::ExceptionInfo
handleLogicError(
    const std::string& status,
    const std::string& callDetails,
    common::FormatType formatType,
    Profile* profile)
{
    maps::wiki::LogicException editorEx(status);
    return handleLogicError(editorEx, callDetails, formatType, profile);
}

const std::string&
mapTopologicalError(const topo::ErrorCode& errorCode)
{
    auto it = TOPO_EXCEPTION_TO_ERROR_CODES.find(errorCode);
    if (it == TOPO_EXCEPTION_TO_ERROR_CODES.end()) {
        it = TOPO_EXCEPTION_TO_ERROR_CODES.find(topo::ErrorCode::Unspecified);
    }
    REQUIRE(it != TOPO_EXCEPTION_TO_ERROR_CODES.end(),
        "Topological error code unknown: " << (int)errorCode);

    return it->second;
}

} // namespace

controller::ExceptionInfo
handleException(
    const std::string& callDetails,
    common::FormatType formatType,
    InternalErrorPolicy internalErrorPolicy,
    Profile* profile)
{
    try {
        throw;
    }
    catch (const maps::wiki::LogicException& ex) {
        return handleLogicError(ex, callDetails, formatType, profile);
    }
    catch (const revision::AlreadyDeletedException& ex) {
        maps::wiki::VersionConflictException editorEx;
        return handleLogicError(editorEx, callDetails, formatType, profile);
    }
    catch (const revision::ConcurrentChangesException& ex) {
        maps::wiki::VersionConflictException editorEx;
        return handleLogicError(editorEx, callDetails, formatType, profile);
    }
    catch (const revision::BranchNotExistsException& ex) {
        return handleLogicError(ERR_BRANCH_NOT_EXISTS, callDetails, formatType, profile);
    }
    catch (const revision::BranchReadOnlyException& ex) {
        return handleLogicError(ERR_BRANCH_READ_ONLY, callDetails, formatType, profile);
    }
    catch (const revision::BranchAlreadyLockedException& ex) {
        return handleLogicError(ERR_BRANCH_LOCKED, callDetails, formatType, profile);
    }
    catch (const revision::StableBranchAlreadyFinishedException& ex) {
        return handleLogicError(ERR_BRANCH_ALREADY_FINISHED, callDetails, formatType, profile);
    }
    catch (const revision::BranchUnavailableException& ex) {
        return handleLogicError(ERR_BRANCH_UNAVAILABLE, callDetails, formatType, profile);
    }
    catch (const revision::BranchInProgressException& ex) {
        return handleLogicError(ERR_BRANCH_IN_PROGRESS, callDetails, formatType, profile);
    }
    catch (const revision::AlreadyRevertedCommitsException& ex) {
        return handleLogicError(ERR_ALREADY_REVERTED_COMMITS, callDetails, formatType, profile);
    }
    catch (const topo::InvalidFaceError& ex) {
        return handleLogicError(ERR_TOPO_INVALID_FACE, callDetails, formatType, profile);
    }
    catch (const topo::GeomProcessingErrorWithLocation& ex) {
        maps::wiki::LogicExceptionWithLocation editorEx(mapTopologicalError(ex.errorCode()), ex.location());
        return handleLogicError(editorEx, callDetails, formatType, profile);
    }
    catch (const topo::GeomProcessingError& ex) {
        return handleLogicError(mapTopologicalError(ex.errorCode()), callDetails, formatType, profile);
    }
    catch(const acl::AccessDenied& ex) {
        return handleLogicError(ERR_FORBIDDEN, callDetails, formatType, profile);
    }
    catch(const acl::UserNotExists& ex) {
        return handleLogicError(ERR_USER_NOT_EXISTS, callDetails, formatType, profile);
    }
    catch(const acl::RoleNotExists& ex) {
        return handleLogicError(ERR_NOT_FOUND, callDetails, formatType, profile);
    }
    catch(const social::SubscriptionNotFound& ex) {
        return handleLogicError(ERR_NOT_FOUND, callDetails, formatType, profile);
    }
    catch(const social::DuplicateSubscription& ex) {
        return handleLogicError(ERR_VERSION_CONFLICT, callDetails, formatType, profile);
    }
    catch(const configs::editor::WrongAttributeValueError& ex) {
        return handleLogicError(ERR_ATTRIBUTE_VALUE_INVALID, callDetails, formatType, profile);
    }
    catch (maps::json::Error& ex) {
        ERROR() << "Json error: " << ex;
        return handleLogicError(ERR_BAD_REQUEST, callDetails, formatType, profile);
    }
    catch (const maps::wiki::controller::ConflictingOperation& ex) {
        return handleLogicError(ERR_CONFLICTING_OPERATION_IN_PROGRESS, callDetails, formatType, profile);
    }
    catch (maps::Exception& ex) {
        ERROR() << "Maps error: " << ex;
        return handleInternalError(ex, callDetails, formatType, internalErrorPolicy, profile);
    }
    catch (const std::exception& ex) {
        ERROR() << "Std error: " << ex.what();
        maps::Exception mapsEx(ex.what());
        return handleInternalError(mapsEx, callDetails, formatType, internalErrorPolicy, profile);
    }
}

} // namespace maps::wiki
