using Fitness_core_BE.DTOs.Auth; using Fitness_core_BE.Exceptions; using Fitness_core_BE.Models.IOs; using System.Text.Json; namespace Fitness_core_BE.Helpers { // Parses AuthHex's real /api/alt|/api/user response shapes. The AuthHex gateway wraps // whatever the underlying handler returns in its own {success,message,data} envelope, so // authResponse.Data (already unwrapped once by AuthService) can itself be a SECOND envelope: // { "success": true, "message": "...", "data": { "referenceNumber": "REC...", "expiresAt": "2026-07-03T18:49:38Z", "recoveryType": "..." } } // referenceNumber is an opaque string, not a Guid, and there is no "expiresInSeconds" field - // it's an absolute "expiresAt" timestamp that we convert to a countdown. public static class AuthOtpResponseParser { public static SendOtpResult ToSendOtpResult(AuthResponse authResponse, int resendAllowedInSeconds = 60) { if (authResponse.Data == null) throw new BadRequestException("Auth service did not return OTP data."); var data = UnwrapToField(DeserializePayload.Deserialize(authResponse.Data), "referenceNumber", "ReferenceNumber"); var referenceNumber = DataExtractor.GetRequiredStringByKeys(data, "referenceNumber"); var expiresAt = DataExtractor.GetOptionalDateTime(data, "expiresAt"); var expiresInSeconds = expiresAt.HasValue ? Math.Max(0, (int)(expiresAt.Value.ToUniversalTime() - DateTime.UtcNow).TotalSeconds) : DataExtractor.GetNullableInt(data, "expiresInSeconds") ?? 600; return new SendOtpResult(referenceNumber, expiresInSeconds, resendAllowedInSeconds); } // If the payload doesn't have any of the fields we're after but looks like another // {success,message,data} envelope, drop down into its "data" and try again. public static Dictionary UnwrapToField(Dictionary data, params string[] anyOfKeys) { for (var depth = 0; depth < 3; depth++) { if (anyOfKeys.Any(k => data.ContainsKey(k))) return data; if (!data.TryGetValue("data", out var nested) || nested.ValueKind != JsonValueKind.Object) return data; data = JsonSerializer.Deserialize>(nested.GetRawText())!; } return data; } } }