using Management.Data;
using Management.Security;
using Management.Services;
using Microsoft.AspNetCore.Mvc;
using System.Text;
using System.Text.Json;
namespace Management.Controllers.Admin;
///
/// Admin endpoints for client lifecycle management.
/// Auth enforced at middleware level (/api/admin/* → Session + Admin role).
/// All operations pass JSON to stored procs (@action/@rqst/@resp).
///
/// APPROVAL FLOW (POST /api/admin/clients):
/// 1. Fetch full applicant from Registration Function — OID comes from server, never browser
/// 2. spClientManagement 'create' → tbClient + tbClientUser + tbClientUserRole (atomic)
/// 3. POST JSON to each provider container → sub-account creation
/// 4. spClientManagement 'recordAdAccount' per provider result
/// 5. spNotification 'queue' → welcome + provisioning alert queued for async send
/// 6. Registration 'complete' → mark registration closed
///
/// ENDPOINTS:
/// GET /api/admin/clients - List clients
/// GET /api/admin/clients/{clientId} - Get client detail
/// POST /api/admin/clients - Approve from registration
/// PUT /api/admin/clients/{clientId} - Update profile
/// POST /api/admin/clients/{clientId}/suspend - Suspend
/// POST /api/admin/clients/{clientId}/cancel - Cancel
/// POST /api/admin/clients/{clientId}/reactivate - Reactivate
/// GET /api/admin/clients/{clientId}/defaults - Wizard pre-fill
///
/// REGISTRATION PROXY:
/// GET /api/registration/pending - List pending applicants
/// GET /api/registration/{id} - Get single applicant
/// POST /api/registration/{id}/reject - Reject applicant
///
[ApiController]
[Route("api/admin/clients")]
public sealed class AdminClientsController : AdminControllerBase
{
private readonly RegistrationClient _registration;
private readonly IHttpClientFactory _http;
private readonly IConfiguration _cfg;
private static readonly JsonSerializerOptions JsonOpts = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
public AdminClientsController(
SqlService sql,
ClientContext client,
RegistrationClient registration,
IHttpClientFactory http,
IConfiguration cfg,
ILogger log)
: base(sql, client, log)
{
_registration = registration;
_http = http;
_cfg = cfg;
}
private const string Proc = "spClientManagement";
// ── CRUD + Lifecycle ──────────────────────────────────────────────────
[HttpPost("list")]
public Task List([FromBody] JsonElement body, CancellationToken ct)
=> CallProc(Proc, "list", body.ToString(), ct);
[HttpGet("{clientId}")]
public Task Get(string clientId, CancellationToken ct)
=> CallProc(Proc, "get", new { clientId }, ct);
///
/// Approve a registration. Only registrationId is needed from the browser —
/// all data including the CIAM OID is fetched from the Registration Function server-side.
///
[HttpPost]
public async Task Approve([FromBody] JsonElement body, CancellationToken ct)
{
var registrationId = Str(body, "registrationId");
if (string.IsNullOrWhiteSpace(registrationId))
return ValidationError("registrationId is required");
// ── 1. Fetch full applicant — OID comes from server record, not browser ──
var regDoc = await _registration.GetByIdAsync(registrationId, ct);
if (regDoc == null)
return StatusCode(502, new { ok = false, error = "Registration service unavailable" });
if (!regDoc.RootElement.TryGetProperty("applicant", out var app))
return NotFound(new { ok = false, error = "Registration not found" });
var entraSub = Str(app, "entraSubjectId");
var regEmail = Str(app, "contactEmail");
var regName = Str(app, "contactName");
var bizName = Str(app, "businessName");
var websiteUrl = Str(app, "websiteUrl");
var bizCategory = Str(app, "businessCategory");
var cltCategory = Str(app, "clientCategory") ?? "General";
var contactPhone= Str(app, "contactPhone");
if (string.IsNullOrWhiteSpace(entraSub))
return BadRequest(new { ok = false, error = "Applicant has no verified identity — they must sign in through the registration portal before approval." });
if (string.IsNullOrWhiteSpace(bizName))
return ValidationError("Registration has no business name");
Logger.LogInformation("[Approve] '{Name}' regId={RegId} OID={OID} by {User}",
bizName, registrationId, entraSub, Client.Email);
// ── 2. Create tbClient + tbClientUser + tbClientUserRole (atomic in proc) ──
var createResp = await Sql.ExecProcAsync("dbo.spClientManagement", "create",
Json(new
{
registrantEntraSub = entraSub,
registrantEmail = regEmail,
registrantName = regName,
name = bizName.Trim(),
websiteUrl,
businessCategory = bizCategory,
contactName = regName,
contactEmail = regEmail,
contactPhone,
clientCategory = cltCategory,
approvedByEmail = Client.Email,
registrationRef = registrationId
}), ct: ct);
using var createDoc = JsonDocument.Parse(createResp);
var cr = createDoc.RootElement;
if (!Bol(cr, "ok"))
return BadRequest(new { ok = false, error = Str(cr, "error") ?? "DB create failed" });
var clientId = Str(cr, "clientId")!;
var wasCreated = Bol(cr, "created");
// ── 3 + 4. Provider sub-accounts ─────────────────────────────────
var providerResults = new List