using Registration.Data; using System.Collections.Concurrent; using Microsoft.Extensions.Logging; namespace Registration.Mock; /// /// In-memory mock registration data for development. /// Persists across requests within a single Function host lifecycle. /// State resets on cold start (by design — it's mock data). /// /// Swap to SqlDataService in Program.cs when dbRegistration is ready. /// public class MockDataService : IRegistrationDataService { private readonly ConcurrentDictionary _store; private readonly ILogger _log; public MockDataService(ILogger log) { _log = log; _store = new ConcurrentDictionary(SeedData()); _log.LogInformation("[Mock] Initialized with {Count} applicants", _store.Count); } public Task GetPendingAsync(CancellationToken ct) { var pending = _store.Values .Where(a => a.Status == "Pending") .OrderBy(a => a.RegisteredUtc) .ToList(); return Task.FromResult(new RegistrationListResult { Ok = true, Applicants = pending, TotalCount = pending.Count }); } public Task GetByIdAsync(string registrationId, CancellationToken ct) { _store.TryGetValue(registrationId, out var applicant); return Task.FromResult(applicant); } public Task RegisterAsync(RegisterRequest request, CancellationToken ct) { if (string.IsNullOrWhiteSpace(request.BusinessName)) return Task.FromResult(new RegistrationResult { Ok = false, Error = "Business name is required" }); // Check for duplicate business name if (_store.Values.Any(a => string.Equals(a.BusinessName, request.BusinessName, StringComparison.OrdinalIgnoreCase) && a.Status != "Rejected")) return Task.FromResult(new RegistrationResult { Ok = false, Error = "A registration with this name already exists" }); var id = Guid.NewGuid().ToString("D"); var applicant = new Applicant { RegistrationId = id, BusinessName = request.BusinessName!.Trim(), WebsiteUrl = request.WebsiteUrl, BusinessCategory = request.BusinessCategory, BusinessDescription = request.BusinessDescription, ContactName = request.ContactName, ContactEmail = request.ContactEmail, ContactPhone = request.ContactPhone, EntraSubjectId = request.EntraSubjectId, Status = "Pending", PaymentVerified = false, RegisteredUtc = DateTime.UtcNow }; _store[id] = applicant; _log.LogInformation("[Mock] New registration: {Name} ({Id})", applicant.BusinessName, id); return Task.FromResult(new RegistrationResult { Ok = true, RegistrationId = id }); } public Task RejectAsync(string registrationId, string? reason, string? rejectedBy, CancellationToken ct) { if (!_store.TryGetValue(registrationId, out var applicant)) return Task.FromResult(new RegistrationResult { Ok = false, Error = "Registration not found" }); if (applicant.Status != "Pending") return Task.FromResult(new RegistrationResult { Ok = false, Error = $"Cannot reject — status is {applicant.Status}" }); applicant.Status = "Rejected"; applicant.RejectionReason = reason; applicant.ReviewedBy = rejectedBy; applicant.ReviewedUtc = DateTime.UtcNow; _log.LogInformation("[Mock] Rejected: {Name} ({Id}) reason={Reason}", applicant.BusinessName, registrationId, reason); return Task.FromResult(new RegistrationResult { Ok = true, RegistrationId = registrationId }); } public Task CompleteAsync(string registrationId, string? platformClientId, CancellationToken ct) { if (!_store.TryGetValue(registrationId, out var applicant)) return Task.FromResult(new RegistrationResult { Ok = false, Error = "Registration not found" }); if (applicant.Status != "Pending") return Task.FromResult(new RegistrationResult { Ok = false, Error = $"Cannot complete — status is {applicant.Status}" }); applicant.Status = "Approved"; applicant.PlatformClientId = platformClientId; applicant.ReviewedUtc = DateTime.UtcNow; _log.LogInformation("[Mock] Approved: {Name} ({Id}) → platform client {PlatformId}", applicant.BusinessName, registrationId, platformClientId); return Task.FromResult(new RegistrationResult { Ok = true, RegistrationId = registrationId }); } // ── Seed Data ─────────────────────────────────── private static IEnumerable> SeedData() { var applicants = new[] { new Applicant { RegistrationId = "reg-001", BusinessName = "Bella's Boutique", WebsiteUrl = "https://bellasboutique.com", BusinessCategory = "retail", BusinessDescription = "Women's fashion and accessories boutique in Orange County. Looking to expand online presence through targeted social and search ads.", ContactName = "Bella Rodriguez", ContactEmail = "bella@bellasboutique.com", ContactPhone = "(714) 555-0142", EntraSubjectId = "entra-mock-bella-001", Status = "Pending", PaymentVerified = true, RegisteredUtc = DateTime.UtcNow.AddDays(-3) }, new Applicant { RegistrationId = "reg-002", BusinessName = "Pacific Coast Plumbing", WebsiteUrl = "https://pacificcoastplumbing.com", BusinessCategory = "home_services", BusinessDescription = "Full-service plumbing company serving LA and OC. Need help with Google Local Services ads and Maps visibility.", ContactName = "Mike Chen", ContactEmail = "mike@pcplumbing.com", ContactPhone = "(562) 555-0198", EntraSubjectId = "entra-mock-mike-002", Status = "Pending", PaymentVerified = true, RegisteredUtc = DateTime.UtcNow.AddDays(-1) }, new Applicant { RegistrationId = "reg-003", BusinessName = "Sunrise Dental Group", WebsiteUrl = "https://sunrisedental.care", BusinessCategory = "healthcare", BusinessDescription = "Multi-location dental practice. Interested in running awareness campaigns for new patient acquisition across Google and Meta.", ContactName = "Dr. Sarah Kim", ContactEmail = "sarah@sunrisedental.care", ContactPhone = "(949) 555-0267", EntraSubjectId = "entra-mock-sarah-003", Status = "Pending", PaymentVerified = false, RegisteredUtc = DateTime.UtcNow.AddHours(-6) }, new Applicant { RegistrationId = "reg-004", BusinessName = "FreshBite Meal Prep", WebsiteUrl = "https://freshbitemealprep.com", BusinessCategory = "food_beverage", BusinessDescription = "Healthy meal prep delivery service. $2k/month budget for Instagram and TikTok ads targeting health-conscious millennials.", ContactName = "Jordan Williams", ContactEmail = "jordan@freshbitemealprep.com", ContactPhone = "(310) 555-0334", EntraSubjectId = "entra-mock-jordan-004", Status = "Pending", PaymentVerified = true, RegisteredUtc = DateTime.UtcNow.AddHours(-2) }, }; return applicants.Select(a => new KeyValuePair(a.RegistrationId, a)); } }