#include <maps/wikimap/mapspro/libs/controller/include/asynctaskcontext.h>
#include <maps/wikimap/mapspro/libs/controller/include/async_tasks_support.h>
#include <maps/libs/pgpool/include/pgpool3.h>
#include <maps/libs/log8/include/log8.h>

namespace maps::wiki::controller {

std::string
AsyncTaskContext::Result::fetch()
{
    REQUIRE(isInternalError_ != IsInternalError::Yes,
        "Async worker failed: " << data_);
    return std::move(data_);
}

AsyncTaskContext::AsyncTaskContext(taskutils::Task task)
    : task_(std::move(task))
{}

AsyncTaskContext::~AsyncTaskContext()
{}

AsyncTaskContext::Result
AsyncTaskContext::run(const AsyncTasksSupport& asyncTasksSupport)
{
    ExceptionInfo exInfo;

    auto makeInternalErrorResult = [&] {
        ASSERT(exInfo.isInternalError() == IsInternalError::Yes);
        return Result(
            std::move(exInfo.what()),
            exInfo.isInternalError()
        );
    };

    auto makeResult = [&](const AsyncTaskResult& asyncTaskResult) {
        if (exInfo.isInternalError() == IsInternalError::Yes) {
            return makeInternalErrorResult();
        }
        return Result(
            formatResult(asyncTaskResult),
            exInfo.isInternalError()
        );
    };

    const auto& token = task_.token();
    if (token.expired()) {
        DEBUG() << "AsyncTaskContext::run task id: " << task_.id() << " skipped, status: expired";
        return makeResult(AsyncTaskResult(token));
    }

    try {
        start(asyncTasksSupport);
        auto taskResult = finish(invokeWorker(), asyncTasksSupport); // hard work ;)
        return makeResult(AsyncTaskResult(token, taskResult));
    } catch (...) {
        exInfo = handleWorkerException();
    }

    try {
        auto taskResult = fail(exInfo.formattedMessage(), asyncTasksSupport); // save failure into db
        return makeResult(AsyncTaskResult(token, taskResult));
    } catch (...) {
        handleWorkerException();
    }

    if (exInfo.isInternalError() == IsInternalError::Yes) {
        return makeInternalErrorResult();
    }

    AsyncTaskResult result(token);
    result.fail(exInfo.formattedMessage());
    return makeResult(result);
}

void
AsyncTaskContext::start(const AsyncTasksSupport& asyncTasksSupport)
{
    createWorker();
    auto work = asyncTasksSupport.corePool().masterWriteableTransaction();
    const auto taskResult = task_.start(*work);
    REQUIRE(taskResult.status() == taskutils::TaskStatus::Started,
        "Task id: " << task_.id()
            << " not started, status: " << taskResult.statusStr());
}

taskutils::TaskResult
AsyncTaskContext::finish(const std::string& result, const AsyncTasksSupport& asyncTasksSupport) const
{
    REQUIRE(!result.empty(), "Empty async task result");
    auto work = asyncTasksSupport.corePool().masterWriteableTransaction();
    return task_.finish(*work, result);
}

taskutils::TaskResult
AsyncTaskContext::fail(const std::string& errorMsg, const AsyncTasksSupport& asyncTasksSupport) const
{
    auto work = asyncTasksSupport.corePool().masterWriteableTransaction();
    return task_.fail(*work, errorMsg);
}

} // namespace maps::wiki::controller
