using Marathon.Application.Abstractions;
using Marathon.Domain.Entities;
using Marathon.Domain.Enums;
using Microsoft.Extensions.Logging;
using DomainEventId = Marathon.Domain.ValueObjects.EventId;
namespace Marathon.Application.UseCases;
///
/// Records a new entered manually via the Journal UI.
///
///
///
/// The use case validates that the referenced event exists, then persists the
/// bet. If the event already has a final result the bet is graded on the spot
/// via — saves the
/// user a round-trip to the resolver page when entering historical wagers.
///
///
public sealed class RecordPlacedBetUseCase
{
private readonly IPlacedBetRepository _bets;
private readonly IEventRepository _events;
private readonly IResultRepository _results;
private readonly ILogger _logger;
public RecordPlacedBetUseCase(
IPlacedBetRepository bets,
IEventRepository events,
IResultRepository results,
ILogger logger)
{
_bets = bets ?? throw new ArgumentNullException(nameof(bets));
_events = events ?? throw new ArgumentNullException(nameof(events));
_results = results ?? throw new ArgumentNullException(nameof(results));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
///
/// Persists . Returns the bet as stored — if the
/// event already has a result, the returned instance reflects the graded
/// .
///
///
/// The bet references an unknown event. The journal does not allow free-form
/// event codes — wagers must be on events the scraper has captured so the
/// CLV calculator can compare against the closing snapshot.
///
public async Task ExecuteAsync(PlacedBet bet, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(bet);
// Confirm the event exists in the local store.
var ev = await _events.GetAsync(bet.EventId, ct).ConfigureAwait(false);
if (ev is null)
{
throw new InvalidOperationException(
$"Cannot record a bet on unknown event '{bet.EventId.Value}'. " +
"The event must already be present in the scrape store.");
}
var toPersist = bet;
// Auto-grade if a result is already available.
if (bet.Outcome == BetOutcome.Pending)
{
var result = await _results.GetAsync(bet.EventId, ct).ConfigureAwait(false);
if (result is not null)
{
var graded = Marathon.Domain.Betting.BetOutcomeResolver.Resolve(bet.Selection, result);
if (graded is not null)
{
toPersist = bet.WithOutcome(graded.Value);
_logger.LogInformation(
"RecordPlacedBetUseCase: bet {BetId} on event {EventId} auto-graded as {Outcome}",
toPersist.Id, ((DomainEventId)toPersist.EventId).Value, graded.Value);
}
}
}
await _bets.AddAsync(toPersist, ct).ConfigureAwait(false);
await _bets.SaveChangesAsync(ct).ConfigureAwait(false);
_logger.LogInformation(
"RecordPlacedBetUseCase: persisted bet {BetId} on event {EventId} stake={Stake} rate={Rate}",
toPersist.Id, ((DomainEventId)toPersist.EventId).Value, toPersist.Stake, toPersist.Selection.Rate.Value);
return toPersist;
}
}