BareGit
#include "series_service.h"

#include <algorithm>
#include <cstdint>
#include <string>
#include <utility>

#include <mw/utils.hpp>

namespace
{

mw::E<void> rejectDuplicate(
    DataSourceInterface& data_source,
    const Series& candidate)
{
    auto series = data_source.getSeries(
        GameContentScope::INCLUDE_INTERNAL);
    if(!series)
    {
        return std::unexpected(std::move(series.error()));
    }
    const bool duplicate = std::ranges::any_of(
        *series,
        [&candidate](const Series& existing)
        {
            return existing.id != candidate.id &&
                existing.game_short_name == candidate.game_short_name &&
                existing.name == candidate.name;
        });
    if(duplicate)
    {
        return std::unexpected(mw::httpError(
            409, "A series with this name already exists for the game"));
    }
    return {};
}

mw::E<void> authorizeAdministrator(
    DataSourceTransactionInterface& transaction,
    const AuthorizationService& authorization,
    std::int64_t actor_user_id)
{
    auto actor = transaction.getUserForUpdate(actor_user_id);
    if(!actor)
    {
        return std::unexpected(std::move(actor.error()));
    }
    if(!*actor || !(*actor)->username ||
       !authorization.canAdminister(**actor))
    {
        return std::unexpected(mw::httpError(403, "Administrator required"));
    }
    return {};
}

} // namespace

SeriesService::SeriesService(DataSourceInterface& data_source)
        : data_source_(data_source)
{}

mw::E<std::int64_t> SeriesService::create(
    std::int64_t actor_user_id,
    std::string game_short_name,
    std::string name,
    std::string description)
{
    name = std::string(mw::strip(name));
    if(name.empty())
    {
        return std::unexpected(mw::httpError(422, "Series name is required"));
    }
    if(name.size() > 200)
    {
        return std::unexpected(mw::httpError(422, "Series name is too long"));
    }
    auto rendered = markdown_renderer_.render(description);
    if(!rendered)
    {
        return std::unexpected(std::move(rendered.error()));
    }
    Series series = {
        0,
        std::move(game_short_name),
        std::move(name),
        std::move(description),
    };
    auto unique = rejectDuplicate(data_source_, series);
    if(!unique)
    {
        return std::unexpected(std::move(unique.error()));
    }
    auto transaction = data_source_.beginTransaction();
    if(!transaction)
    {
        return std::unexpected(std::move(transaction.error()));
    }
    auto authorized = authorizeAdministrator(
        **transaction, authorization_, actor_user_id);
    if(!authorized)
    {
        return std::unexpected(std::move(authorized.error()));
    }
    auto game = (*transaction)->getGameDefinitionForUpdate(
        series.game_short_name);
    if(!game)
    {
        return std::unexpected(std::move(game.error()));
    }
    if(!*game)
    {
        return std::unexpected(mw::httpError(422, "Unknown game"));
    }
    auto id = (*transaction)->insertSeries(series);
    if(!id)
    {
        return std::unexpected(std::move(id.error()));
    }
    auto committed = (*transaction)->commit();
    if(!committed)
    {
        return std::unexpected(std::move(committed.error()));
    }
    return *id;
}

mw::E<void> SeriesService::update(
    std::int64_t actor_user_id,
    std::int64_t series_id,
    std::string name,
    std::string description)
{
    auto current = data_source_.getSeries(
        series_id, GameContentScope::INCLUDE_INTERNAL);
    if(!current)
    {
        return std::unexpected(std::move(current.error()));
    }
    if(!*current)
    {
        return std::unexpected(mw::httpError(404, "Series not found"));
    }
    Series series = std::move(**current);
    series.name = std::string(mw::strip(name));
    series.description = std::move(description);
    if(series.name.empty())
    {
        return std::unexpected(mw::httpError(422, "Series name is required"));
    }
    if(series.name.size() > 200)
    {
        return std::unexpected(mw::httpError(422, "Series name is too long"));
    }
    auto rendered = markdown_renderer_.render(series.description);
    if(!rendered)
    {
        return std::unexpected(std::move(rendered.error()));
    }
    auto unique = rejectDuplicate(data_source_, series);
    if(!unique)
    {
        return std::unexpected(std::move(unique.error()));
    }
    auto transaction = data_source_.beginTransaction();
    if(!transaction)
    {
        return std::unexpected(std::move(transaction.error()));
    }
    auto authorized = authorizeAdministrator(
        **transaction, authorization_, actor_user_id);
    if(!authorized)
    {
        return std::unexpected(std::move(authorized.error()));
    }
    auto updated = (*transaction)->updateSeries(series);
    if(!updated)
    {
        return std::unexpected(std::move(updated.error()));
    }
    return (*transaction)->commit();
}

mw::E<void> SeriesService::remove(
    std::int64_t actor_user_id,
    std::int64_t series_id)
{
    auto current = data_source_.getSeries(
        series_id, GameContentScope::INCLUDE_INTERNAL);
    if(!current)
    {
        return std::unexpected(std::move(current.error()));
    }
    if(!*current)
    {
        return std::unexpected(mw::httpError(404, "Series not found"));
    }
    auto transaction = data_source_.beginTransaction();
    if(!transaction)
    {
        return std::unexpected(std::move(transaction.error()));
    }
    auto authorized = authorizeAdministrator(
        **transaction, authorization_, actor_user_id);
    if(!authorized)
    {
        return std::unexpected(std::move(authorized.error()));
    }
    auto deleted = (*transaction)->deleteSeries(series_id);
    if(!deleted)
    {
        return std::unexpected(std::move(deleted.error()));
    }
    return (*transaction)->commit();
}