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;
}
}
}