using FluentAssertions;
using Marathon.Domain.Betting;
using Marathon.Domain.Entities;
using Marathon.Domain.Enums;
using Marathon.Domain.ValueObjects;
namespace Marathon.Domain.Tests.Betting;
///
/// Unit tests for across every bet type +
/// every important boundary (handicap push, total push, period-scope null).
///
public sealed class BetOutcomeResolverTests
{
private static readonly EventId EventId = new("12345678");
// ── Win bets ─────────────────────────────────────────────────────────────
[Theory]
[InlineData(Side.Side1, Side.Side1, BetOutcome.Won)]
[InlineData(Side.Side1, Side.Side2, BetOutcome.Lost)]
[InlineData(Side.Side1, Side.Draw, BetOutcome.Lost)]
[InlineData(Side.Side2, Side.Side2, BetOutcome.Won)]
[InlineData(Side.Side2, Side.Side1, BetOutcome.Lost)]
public void Should_GradeWinBet(Side selectionSide, Side winner, BetOutcome expected)
{
var bet = MakeBet(BetType.Win, selectionSide);
var result = MakeResult(winner);
BetOutcomeResolver.Resolve(bet, result).Should().Be(expected);
}
// ── Draw bets ────────────────────────────────────────────────────────────
[Theory]
[InlineData(Side.Draw, BetOutcome.Won)]
[InlineData(Side.Side1, BetOutcome.Lost)]
[InlineData(Side.Side2, BetOutcome.Lost)]
public void Should_GradeDrawBet(Side winner, BetOutcome expected)
{
var bet = MakeBet(BetType.Draw, Side.Draw);
var result = MakeResult(winner, 1, 1);
BetOutcomeResolver.Resolve(bet, result).Should().Be(expected);
}
// ── Handicap (WinFora) ───────────────────────────────────────────────────
[Theory]
// Side1 with +1.5 handicap; final 0-2 → adjusted 1.5 vs 2 → loss
[InlineData(Side.Side1, 1.5, 0, 2, BetOutcome.Lost)]
// Side1 with +1.5; final 1-2 → adjusted 2.5 vs 2 → win
[InlineData(Side.Side1, 1.5, 1, 2, BetOutcome.Won)]
// Side1 with -1.5; final 3-1 → adjusted 1.5 vs 1 → win
[InlineData(Side.Side1, -1.5, 3, 1, BetOutcome.Won)]
// Whole-number handicap that ties: Side1 +1, final 1-2 → 2 vs 2 → push (Void)
[InlineData(Side.Side1, 1, 1, 2, BetOutcome.Void)]
// Side2 with -1; final 0-2 → adjusted Side2 1 vs Side1 0 → win
[InlineData(Side.Side2, -1, 0, 2, BetOutcome.Won)]
public void Should_GradeHandicapBet(
Side side, double handicap, int s1, int s2, BetOutcome expected)
{
var bet = new Bet(MatchScope.Instance, BetType.WinFora, side,
new OddsValue((decimal)handicap), new OddsRate(1.85m));
var result = new EventResult(EventId, s1, s2,
DeriveWinner(s1, s2), DateTimeOffset.UtcNow);
BetOutcomeResolver.Resolve(bet, result).Should().Be(expected);
}
// ── Totals ───────────────────────────────────────────────────────────────
[Theory]
// Over 2.5; total 3 → win
[InlineData(Side.More, 2.5, 1, 2, BetOutcome.Won)]
// Over 2.5; total 2 → loss
[InlineData(Side.More, 2.5, 0, 2, BetOutcome.Lost)]
// Over 3.0; total 3 → push (Void)
[InlineData(Side.More, 3.0, 1, 2, BetOutcome.Void)]
// Under 2.5; total 2 → win
[InlineData(Side.Less, 2.5, 1, 1, BetOutcome.Won)]
// Under 2.5; total 3 → loss
[InlineData(Side.Less, 2.5, 1, 2, BetOutcome.Lost)]
public void Should_GradeTotalBet(
Side side, double threshold, int s1, int s2, BetOutcome expected)
{
var bet = new Bet(MatchScope.Instance, BetType.Total, side,
new OddsValue((decimal)threshold), new OddsRate(1.85m));
var result = new EventResult(EventId, s1, s2,
DeriveWinner(s1, s2), DateTimeOffset.UtcNow);
BetOutcomeResolver.Resolve(bet, result).Should().Be(expected);
}
// ── Period-scope guard ───────────────────────────────────────────────────
[Fact]
public void Should_ReturnNull_When_BetScopeIsPeriod()
{
var bet = new Bet(new PeriodScope(1), BetType.Win, Side.Side1,
value: null, new OddsRate(2.10m));
var result = MakeResult(Side.Side1);
BetOutcomeResolver.Resolve(bet, result).Should().BeNull(
"period scope cannot be graded from full-time score alone");
}
[Fact]
public void Should_Throw_When_BetOrResultIsNull()
{
((Action)(() => BetOutcomeResolver.Resolve(null!, MakeResult(Side.Side1))))
.Should().Throw();
((Action)(() => BetOutcomeResolver.Resolve(MakeBet(BetType.Win, Side.Side1), null!)))
.Should().Throw();
}
// ── Helpers ──────────────────────────────────────────────────────────────
private static Bet MakeBet(BetType type, Side side) =>
new(MatchScope.Instance, type, side, value: null, new OddsRate(2.00m));
private static EventResult MakeResult(Side winner, int s1 = 1, int s2 = 0) =>
new(EventId, s1, s2, winner, DateTimeOffset.UtcNow);
private static Side DeriveWinner(int s1, int s2) =>
s1 == s2 ? Side.Draw : (s1 > s2 ? Side.Side1 : Side.Side2);
}