using LiteCharms.Features.Abstractions; using LiteCharms.Features.MidrandBooks.AuthorBooks.Models; using LiteCharms.Features.MidrandBooks.Extensions; using LiteCharms.Features.MidrandBooks.Postgres; namespace LiteCharms.Features.MidrandBooks.AuthorBooks; public sealed class BooksService(IDbContextFactory contextFactory) : IService { public async ValueTask UpdateBookStatusAsync(long bookId, bool isEnabled, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var rowsUpdated = await context.Books .Where(b => b.Id == bookId) .ExecuteUpdateAsync(setters => setters .SetProperty(b => b.Enabled, isEnabled) .SetProperty(b => b.UpdatedAt, DateTime.UtcNow), cancellationToken); return rowsUpdated > 0 ? Result.Ok() : Result.Fail(new Error($"Book with ID {bookId} not found")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> CreateBookAsync(long authorId, long productId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if(!await context.Authors.AnyAsync(a => a.Id == authorId, cancellationToken)) return Result.Fail("Author not found."); if (!await context.Products.AnyAsync(p => p.Id == productId, cancellationToken)) return Result.Fail("Product not found."); var book = context.Books.Add(new Entities.AuthorBook { CreatedAt = DateTime.UtcNow, AuthorId = authorId, ProductId = productId }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(book.Entity.Id) : Result.Fail("Failed to create book."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetBookByProductIdAsync(long productId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var book = await context.Books .AsNoTracking() .Include(b => b.Author) .Include(b => b.Product) .ThenInclude(b => b!.Prices) .Include(b => b.Pages) .FirstOrDefaultAsync(b => b.ProductId == productId, cancellationToken); return book is null ? Result.Fail(new Error($"Book with product ID {productId} not found")) : Result.Ok(book.ToModel()); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetBookAsync(long bookId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var book = await context.Books .AsNoTracking() .Include(b => b.Author) .Include(b => b.Product) .ThenInclude(b => b!.Prices) .Include(b => b.Pages) .FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken); return book is null ? Result.Fail(new Error($"Book with ID {bookId} not found")) : Result.Ok(book.ToModel()); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetBooksByAuthorAsync(long authorId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if(!await context.Authors.AnyAsync(a => a.Id == authorId, cancellationToken)) return Result.Fail(new Error($"Author with ID {authorId} not found")); var books = await context.Books .AsNoTracking() .Include(b => b.Author) .Include(b => b.Product) .ThenInclude(b => b!.Prices) .OrderByDescending(b => b.CreatedAt) .Where(b => b.AuthorId == authorId) .ToListAsync(cancellationToken); return books?.Count > 0 ? Result.Ok(books.Select(b => b.ToModel()).ToArray()) : Result.Fail(new Error($"No books found for author with ID {authorId}")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetPublishedBooksAsync(int offset, int limit, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var books = await context.Books .AsNoTracking() .Include(b => b.Author) .Include(b => b.Product) .ThenInclude(b => b!.Prices) .Include(b => b.Pages) .Where(b => b.Enabled && b.Product!.Enabled && b.Author!.Enabled) .OrderByDescending(b => b.Ranking) .ThenByDescending(b => b.Ranking) .ThenByDescending(b => b.CreatedAt) .ThenByDescending(b => b.UpdatedAt) .Skip(offset).Take(limit) .AsSplitQuery() .ToArrayAsync(cancellationToken); return books?.Length > 0 ? Result.Ok(books.Select(b => b.ToModel()).ToArray()) : Result.Fail(new Error("No published books found.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } }