using LiteCharms.Features.MidrandBooks.AuthorBooks; using LiteCharms.Features.MidrandBooks.Authors; using LiteCharms.Features.MidrandBooks.Products; using LiteCharms.Features.MidrandBooks.Seed.Configuration; namespace LiteCharms.Features.MidrandBooks.Seed; public sealed class ProductsSeederService(ProductService productService, AuthorService authorService, BooksService booksService, IFeatureManager features, IOptions options, ILogger logger) : BackgroundService { private readonly CdnSettings cdnSettings = options.Value; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (await features.IsEnabledAsync("ProductsSeederService") is not true) return; logger.LogInformation("Product Seeding started"); if (cdnSettings.BookCovers is null || cdnSettings.BookCovers.Length == 0) { logger.LogWarning("No book covers found in CDN settings. Seeding aborted."); return; } // Initialize Bogus Faker engine var faker = new Faker(); var culture = CultureInfo.InvariantCulture; // Ensure repeatable data sets if run multiple times by anchoring the seed Randomizer.Seed = new Random(42); foreach (var bookCover in cdnSettings.BookCovers) { if (stoppingToken.IsCancellationRequested) break; // Generate beautifully mixed eclectic topics on the fly var bookTopic = faker.PickRandom( // --- Tech & IT --- "C# 12 & Modern .NET Architecture", "PostgreSQL Database Optimization", "Docker & Kubernetes in Production", "Domain-Driven Design Paradigms", "Artificial Intelligence with Python", // --- Sci-Fi & Fantasy --- "The Chronicles of the Quantum Nebula", "Legends of the Lost Cybernetic Kingdom", "Parallel Dimensions and Rogue Time Streams", "The Last Android in Neo-Johannesburg", // --- Thrillers, Mystery & Crime --- "The Midnight Code Cryptograph", "Shadows in the Highveld", "The Silent Witness of Midrand", "Deception on the 14th Floor", // --- Business, Finance & Wealth --- "Mastering the South African Tech Market", "The Modern Entrepreneur's Blueprint", "Generational Wealth and Venture Capital", "Negotiation Tactics for High-Stakes Deals", // --- Self-Help & Personal Growth --- "The Art of Relentless Focus", "Building High-Performance Habits", "The Mindfulness Guide for Software Engineers", "Unlocking Creative Flow Under Pressure" ); // Dynamic raw title generation formulas executed via random function picker var titlePatterns = new Func[] { () => $"{faker.Company.CatchPhrase()} with {bookTopic}", () => $"The {faker.Commerce.ProductAdjective()} Guide to {bookTopic}", () => $"Mastering {bookTopic}: A {faker.Company.Bs()} Blueprint", () => $"{bookTopic} for the Modern {faker.Name.JobTitle()}", () => $"Advanced {bookTopic}: Demystifying the {faker.Company.CatchPhrase()}", () => $"{faker.Random.Replace("###")} Blueprints for {bookTopic}" }; // Pick a format template and resolve it down to raw string text var rawTitle = faker.PickRandom(titlePatterns)(); var bookTitle = rawTitle.Length > 255 ? rawTitle[..252] + "..." : rawTitle; var rawSummary = $"A comprehensive guide to mastering {bookTopic}. Learn modern implementation techniques through real-world software engineering paradigms."; var bookSummary = rawSummary.Length > 512 ? rawSummary[..509] + "..." : rawSummary; // Generating a single concise paragraph ensures a rich text description falling safely well under 1024 var rawDescription = faker.Lorem.Paragraph(3); var bookDescription = rawDescription.Length > 1024 ? rawDescription[..1021] + "..." : rawDescription; var authorFirstName = faker.Name.FirstName(); var authorLastName = faker.Name.LastName(); var publisherCompany = faker.Company.CompanyName(); // Safe bounded random picking for book thumbnails string? pickedBookThumbnail = null; string? pickedBookThumbnail1 = null; string? pickedBookThumbnail2 = null; string? pickedBookThumbnail3 = null; string? pickedBookThumbnail4 = null; if (cdnSettings.BookThumbnails is not null && cdnSettings.BookThumbnails.Length > 0) { pickedBookThumbnail = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.BookThumbnails)}"; pickedBookThumbnail1 = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.BookThumbnails)}"; pickedBookThumbnail2 = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.BookThumbnails)}"; pickedBookThumbnail3 = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.BookThumbnails)}"; pickedBookThumbnail4 = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.BookThumbnails)}"; } // Step 1: Add Product var productCreateResult = await productService.CreateProductAsync(new Products.Models.CreateProduct { Name = bookTitle, Summary = bookSummary, Description = bookDescription, ImageUrl = $"{cdnSettings.BaseCdn}{bookCover}", Type = ProductTypes.Book, Metadata = new Models.ProductMetadata { CopyrightInfo = $"© {DateTime.UtcNow.Year} {publisherCompany}. All rights reserved.", ManufactureDate = faker.Date.Past(3).ToString("yyyy-MM-dd", culture), Manufacturer = $"{authorFirstName} {authorLastName} / {publisherCompany}", SerialNumber = faker.Phone.PhoneNumber("978-##########") }, Categories = ["Coding", "Computers", "IT"], ThumbnailUrls = pickedBookThumbnail is not null ? [pickedBookThumbnail, pickedBookThumbnail1!, pickedBookThumbnail2!, pickedBookThumbnail3!, pickedBookThumbnail4!] : null }, stoppingToken); if (productCreateResult.IsFailed) { logger.LogError("Failed to create product: {Error}", productCreateResult.Errors[0].Message); break; } // Step 2: Enable product so it can show on the shop var enableProductResult = await productService.UpdateProductStatusAsync(productId: productCreateResult.Value, isEnabled: true, stoppingToken); if (enableProductResult.IsFailed) { logger.LogError("Failed to enable created product: {Error}", enableProductResult.Errors[0].Message); break; } // Step 3: Create Product Price var productPriceCreateResult = await productService.CreateProductPriceAsync(productId: productCreateResult.Value, request: new Products.Models.CreateProductPrice { Amount = Math.Round(faker.Random.Decimal(150m, 650m), 2), Discount = 0.0m }, stoppingToken); if (productPriceCreateResult.IsFailed) { logger.LogError("Failed to create product price: {Error}", productPriceCreateResult.Errors[0].Message); break; } // Safe bounded picking for Authors (Real Avatars) string authorAvatarUrl = faker.Internet.Avatar(); // Fallback if (cdnSettings.Authors is not null && cdnSettings.Authors.Length > 0) { authorAvatarUrl = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.Authors)}"; } // Safe bounded picking for Author Thumbnails (Cartoon Avatars) string? authorThumbnailUrl = null; if (cdnSettings.AuthorThumbnails is not null && cdnSettings.AuthorThumbnails.Length > 0) { var selectedThumb = faker.PickRandom(cdnSettings.AuthorThumbnails); authorThumbnailUrl = $"{cdnSettings.BaseCdn}{selectedThumb}.jpg"; } // Synthesize a highly dynamic, organic opening bio statement var professionalBackgrounds = new[] { $"{authorFirstName} {authorLastName} is an award-winning {faker.Name.JobDescriptor()} {faker.Name.JobTitle()} with over {faker.Random.Number(5, 25)} years of core engineering domain expertise.", $"As a veteran systems consultant and practicing {faker.Name.JobTitle()}, {authorFirstName} has spent decades leading digital infrastructure transformations and managing complex topologies.", $"Operating from modern innovation hubs, {authorFirstName} {authorLastName} specializes in global product strategies and serves as an authority in {faker.Name.JobDescriptor()} computing.", $"With a rich professional background as a principal {faker.Name.JobTitle()} at {publisherCompany}, {authorFirstName} has spent a lifetime refining the system workflows highlighted here." }; // Pick a randomized context hook and append a 2-paragraph contextual narrative block var biographyPrefix = faker.PickRandom(professionalBackgrounds); var authorBiography = $"{biographyPrefix} {faker.Lorem.Paragraph(2)}"; // Step 4: Create Author var authorCreateResult = await authorService.CreateAuthorAsync(request: new Authors.Models.CreateAuthor { Name = authorFirstName, LastName = authorLastName, Company = publisherCompany, VatNumber = faker.Random.Bool() ? faker.Phone.PhoneNumber("4#########") : "", PublisherType = faker.PickRandom(), Email = faker.Internet.Email(authorFirstName, authorLastName), Website = faker.Internet.Url(), ImageUrl = authorAvatarUrl, SocialMedia = [ new Models.SocialMedia { Name = "LinkedIn", ImageUrl = "https://cdn.example.com/icons/linkedin.png", Type = SocialMediaTypes.LinkedIn, Url = $"https://linkedin.com/in/{authorFirstName.ToLower(culture)}-{authorLastName.ToLower(culture)}" }, new Models.SocialMedia { Name = "GitHub", ImageUrl = "https://cdn.example.com/icons/github.png", Type = SocialMediaTypes.GitHub, Url = $"https://github.com/tech-{authorFirstName.ToLower(culture)}" } ], Biography = authorBiography, ThumbnailImageUrl = authorThumbnailUrl }, stoppingToken); if (authorCreateResult.IsFailed) { logger.LogError("Failed to create author: {Error}", authorCreateResult.Errors[0].Message); break; } // Step 5: Create Author-Book link (product linkage) var authorBookCreateResult = await booksService.CreateBookAsync(authorId: authorCreateResult.Value, productId: productCreateResult.Value, stoppingToken); if (authorBookCreateResult.IsFailed) { logger.LogError("Failed to create author-book linkage: {Error}", authorBookCreateResult.Errors[0].Message); break; } var enableAuthorBookResult = await booksService.UpdateBookStatusAsync(bookId: authorBookCreateResult.Value, isEnabled: true, stoppingToken); if (enableAuthorBookResult.IsFailed) { logger.LogError("Failed to enable author-book link: {Error}", enableAuthorBookResult.Errors[0].Message); break; } logger.LogInformation("Successfully seeded book product: {Title}", bookTitle); } logger.LogInformation("Product Seeding completed successfully."); } }