using System.Net; using System.Net.Http.Json; using ChessCubing.App.Models; using ChessCubing.App.Models.Stats; namespace ChessCubing.App.Services; public sealed class MatchStatsService(HttpClient httpClient) { private readonly HttpClient _httpClient = httpClient; public async Task TryReportCompletedMatchAsync(MatchState? match, CancellationToken cancellationToken = default) { if (match is null || string.IsNullOrWhiteSpace(match.Result) || match.ResultRecordedUtc is not null) { return false; } if (string.IsNullOrWhiteSpace(match.WhiteSubject) && string.IsNullOrWhiteSpace(match.BlackSubject)) { return false; } var request = BuildReport(match); try { var response = await _httpClient.PostAsJsonAsync("api/users/me/stats/matches", request, cancellationToken); if (response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) { return false; } if (!response.IsSuccessStatusCode) { return false; } match.ResultRecordedUtc = DateTime.UtcNow; return true; } catch { return false; } } private static ReportCompletedMatchRequest BuildReport(MatchState match) => new() { MatchId = string.IsNullOrWhiteSpace(match.MatchId) ? Guid.NewGuid().ToString("N") : match.MatchId, CollaborationSessionId = match.CollaborationSessionId, WhiteSubject = NormalizeOptional(match.WhiteSubject), WhiteName = MatchEngine.PlayerName(match, MatchEngine.ColorWhite), BlackSubject = NormalizeOptional(match.BlackSubject), BlackName = MatchEngine.PlayerName(match, MatchEngine.ColorBlack), Result = match.Result ?? string.Empty, Mode = match.Config.Mode, Preset = match.Config.Preset, MatchLabel = MatchEngine.SanitizeText(match.Config.MatchLabel), BlockNumber = Math.Max(1, match.BlockNumber), WhiteMoves = Math.Max(0, match.Moves.White), BlackMoves = Math.Max(0, match.Moves.Black), CubeRounds = match.Cube.History .Select(entry => new ReportCompletedCubeRound { BlockNumber = Math.Max(1, entry.BlockNumber), Number = entry.Number, White = entry.White, Black = entry.Black, }) .ToArray(), }; private static string? NormalizeOptional(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); }