using System; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; namespace RecruitmentBLL.Integration { /// public class DxpAuthIntegrationService : IDxpAuthIntegrationService { private readonly IHttpClientFactory _HttpClientFactory; private readonly ILogger _Logger; // Same "trailing integer token" convention EcpIntegrationService.TrailingIdRegex already // relies on for SuccessResponse.SaveSuccessMessage-style strings. private static readonly Regex TrailingIdRegex = new(@"(-?\d+)\s*$", RegexOptions.Compiled); public DxpAuthIntegrationService( IHttpClientFactory httpClientFactory, ILogger logger) { _HttpClientFactory = httpClientFactory; _Logger = logger; } public async Task RegisterCandidateUserAsync( string fullName, string email, string? mobile, string password, CancellationToken ct) { try { GB5Trace.Step("dxp-register-candidate-user", new { email }); var client = _HttpClientFactory.CreateClient("DXPModule"); // RegisterParameters shape (DXPSL.Parameters.Auth.RegisterParameters) posted // directly at top level — Register is a plain Endpoint<,>, not BaseEndpoint, so // no Login header/wrapper is involved (see this interface's doc comment). var payload = new { FullName = fullName, Email = email, Mobile = mobile, Password = password }; var response = await client.PostAsJsonAsync("DXP/Auth/Register", payload, ct).ConfigureAwait(false); var raw = await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { var error = ExtractErrorBody(raw) ?? $"DXP returned {(int)response.StatusCode}"; _Logger.LogWarning("DXP Register failed for {Email}: {StatusCode} {Error}", email, response.StatusCode, error); return new DxpAuthOutcome { Success = false, Error = error }; } var body = ExtractBody(raw); var dxpUserId = ExtractTrailingId(body); return new DxpAuthOutcome { Success = true, DxpUserId = dxpUserId ?? -1 }; } catch (Exception ex) { GB5Trace.MarkFailed("dxp-register-candidate-user-failed", ex); _Logger.LogError(ex, "DXP RegisterCandidateUserAsync failed for {Email}", email); return new DxpAuthOutcome { Success = false, Error = ex.Message }; } } public async Task LoginCandidateAsync(string email, string password, CancellationToken ct) { try { GB5Trace.Step("dxp-login-candidate-user", new { email }); var client = _HttpClientFactory.CreateClient("DXPModule"); // LoginParameters shape (DXPSL.Parameters.Auth.LoginParameters) posted directly // at top level. DeviceInfo is informational only (persisted onto // MDXPREFRESHTOKEN.DEVICEINFO) — irrelevant here since a partyless candidate // account never gets a real refresh token (see this interface's doc comment). var payload = new { Email = email, Password = password, DeviceInfo = "RecruitmentCandidatePortal" }; var response = await client.PostAsJsonAsync("DXP/Auth/Login", payload, ct).ConfigureAwait(false); var raw = await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { // Deliberately generic on our side too — DXP's own Login.cs never reveals // whether the email exists; we don't either. var error = ExtractErrorBody(raw) ?? "Invalid email or password."; _Logger.LogWarning("DXP Login failed for {Email}: {StatusCode}", email, response.StatusCode); return new DxpLoginOutcome { Success = false, Error = error }; } using var doc = JsonDocument.Parse(raw); if (!doc.RootElement.TryGetProperty("Body", out var bodyElement) || bodyElement.ValueKind != JsonValueKind.Object) { _Logger.LogWarning("DXP Login succeeded but response body was not the expected DXPLoginResultDTO shape for {Email}", email); return new DxpLoginOutcome { Success = false, Error = "Unexpected DXP login response shape." }; } var dxpUserId = bodyElement.TryGetProperty("DxpUserId", out var idEl) && idEl.ValueKind == JsonValueKind.Number ? idEl.GetInt32() : -1; var fullName = bodyElement.TryGetProperty("FullName", out var nameEl) && nameEl.ValueKind == JsonValueKind.String ? nameEl.GetString() ?? string.Empty : string.Empty; return new DxpLoginOutcome { Success = true, DxpUserId = dxpUserId, FullName = fullName }; } catch (Exception ex) { GB5Trace.MarkFailed("dxp-login-candidate-user-failed", ex); _Logger.LogError(ex, "DXP LoginCandidateAsync failed for {Email}", email); return new DxpLoginOutcome { Success = false, Error = ex.Message }; } } // ── Helpers — same permissive-parse idiom as EcpIntegrationService's Extract* helpers ── private static string? ExtractBody(string raw) { try { using var doc = JsonDocument.Parse(raw); if (doc.RootElement.TryGetProperty("Body", out var bodyElement)) return bodyElement.ValueKind == JsonValueKind.String ? bodyElement.GetString() : bodyElement.GetRawText(); return null; } catch { return null; } } private static string? ExtractErrorBody(string raw) { try { using var doc = JsonDocument.Parse(raw); return doc.RootElement.TryGetProperty("ErrorBody", out var errorElement) ? errorElement.GetString() : null; } catch { return null; } } private static int? ExtractTrailingId(string? body) { if (body is null) return null; var match = TrailingIdRegex.Match(body); return match.Success && int.TryParse(match.Groups[1].Value, out var id) ? id : null; } } }