using Data.SeaHavenIndustries; using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; using SeaHaven.DataServices.Interfaces; namespace SeaHaven.DataServices.Implementation { public class VendorDataService : IVendorDataService { private readonly ApplicationDbContext _context; public VendorDataService(ApplicationDbContext context) { _context = context; } public async Task GetByIdAsync(int id) { return await _context.Vendors .Include(v => v.Dispatches!) .ThenInclude(dispatch => dispatch.DispatchWorkOrders) .FirstOrDefaultAsync(v => v.Id == id); } public async Task GetByIdWithDetailsAsync(int id) { return await _context.Vendors .Include(v => v.Dispatches!) .ThenInclude(dispatch => dispatch.DispatchWorkOrders) .FirstOrDefaultAsync(v => v.Id == id); } public async Task> GetAllAsync() { return await _context.Vendors.ToListAsync(); } public async Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( int page, int pageSize, string? search = null, bool? isActive = true, IReadOnlyCollection? companies = null, IReadOnlyCollection? trades = null, IReadOnlyCollection? locations = null, IReadOnlyCollection? jobBuckets = null) { search ??= ""; var query = _context.Vendors.Where(v => v.IsDeleted == null || v.IsDeleted == false); if (isActive.HasValue) query = query.Where(v => v.IsActive == isActive.Value); if (!string.IsNullOrWhiteSpace(search)) { var s = search.ToLower(); query = query.Where(v => (v.CompanyName ?? "").ToLower().Contains(s) || (v.ContactName ?? "").ToLower().Contains(s) || (v.Email ?? "").ToLower().Contains(s) || (v.TradeSpecialties ?? "").ToLower().Contains(s) || (v.City ?? "").ToLower().Contains(s) || (v.State ?? "").ToLower().Contains(s) || (v.Zip ?? "").ToLower().Contains(s)); } var selectedCompanies = companies? .Where(value => !string.IsNullOrWhiteSpace(value)) .Select(value => value.Trim().ToLower()) .Distinct() .ToArray() ?? Array.Empty(); if (selectedCompanies.Length > 0) query = query.Where(v => selectedCompanies.Contains((v.CompanyName ?? "").ToLower())); var selectedTrades = trades? .Where(value => !string.IsNullOrWhiteSpace(value)) .Select(value => value.Trim().ToLower()) .Distinct() .ToArray() ?? Array.Empty(); if (selectedTrades.Length > 0) query = query.Where(BuildTradePredicate(selectedTrades)); var selectedLocations = locations? .Where(value => !string.IsNullOrWhiteSpace(value)) .Select(value => value.Trim().ToLower()) .Distinct() .ToArray() ?? Array.Empty(); if (selectedLocations.Length > 0) query = query.Where(v => selectedLocations.Contains(((v.City ?? "") + ", " + (v.State ?? "")).ToLower())); var selectedJobBuckets = jobBuckets? .Where(value => !string.IsNullOrWhiteSpace(value)) .Select(value => value.Trim().ToLower()) .Distinct() .ToArray() ?? Array.Empty(); if (selectedJobBuckets.Length > 0) { var under50 = selectedJobBuckets.Contains("under-50"); var from50To99 = selectedJobBuckets.Contains("50-99"); var from100To149 = selectedJobBuckets.Contains("100-149"); var from150 = selectedJobBuckets.Contains("150-plus"); var vendorsWithDispatches = await query .Include(v => v.Dispatches!) .ThenInclude(dispatch => dispatch.DispatchWorkOrders) .OrderByDescending(v => v.Id) .ToListAsync(); var filtered = vendorsWithDispatches.Where(vendor => { var totalJobs = CountDistinctWorkOrders(vendor); return (under50 && totalJobs < 50) || (from50To99 && totalJobs is >= 50 and <= 99) || (from100To149 && totalJobs is >= 100 and <= 149) || (from150 && totalJobs >= 150); }).ToList(); return ( filtered.Skip((page - 1) * pageSize).Take(pageSize), filtered.Count); } var totalCount = await query.CountAsync(); var items = await query .Include(v => v.Dispatches!) .ThenInclude(dispatch => dispatch.DispatchWorkOrders) .OrderByDescending(v => v.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); return (items, totalCount); } private static int CountDistinctWorkOrders(Vendor vendor) { if (vendor.Dispatches == null) return 0; return vendor.Dispatches .SelectMany(dispatch => { var linked = dispatch.DispatchWorkOrders?.Select(item => item.WorkOrderId) ?? Enumerable.Empty(); return dispatch.WorkOrderId.HasValue ? linked.Append(dispatch.WorkOrderId.Value) : linked; }) .Distinct() .Count(); } private static Expression> BuildTradePredicate( IReadOnlyCollection selectedTrades) { var vendor = Expression.Parameter(typeof(Vendor), "vendor"); var specialties = Expression.Coalesce( Expression.Property(vendor, nameof(Vendor.TradeSpecialties)), Expression.Constant(string.Empty)); var lowered = Expression.Call(specialties, nameof(string.ToLower), Type.EmptyTypes); Expression? body = null; foreach (var trade in selectedTrades) { var contains = Expression.Call( lowered, nameof(string.Contains), Type.EmptyTypes, Expression.Constant(trade)); body = body == null ? contains : Expression.OrElse(body, contains); } return Expression.Lambda>(body ?? Expression.Constant(false), vendor); } public async Task AddAsync(Vendor vendor) { await _context.Vendors.AddAsync(vendor); await _context.SaveChangesAsync(); return vendor; } public async Task UpdateAsync(Vendor vendor) { _context.Vendors.Update(vendor); await _context.SaveChangesAsync(); } public async Task DeleteAsync(int id) { var entity = await GetByIdAsync(id); if (entity != null) { _context.Vendors.Remove(entity); await _context.SaveChangesAsync(); } } public async Task ExistsAsync(int id) { return await _context.Vendors.AnyAsync(v => v.Id == id); } public async Task GetActiveTokenAsync(int vendorId) { return await _context.VendorAccessTokens .FirstOrDefaultAsync(t => t.VendorId == vendorId); } public async Task GetTokenByStringAsync(string token) { return await _context.VendorAccessTokens .FirstOrDefaultAsync(t => t.Token == token); } public async Task CountAsync() { return await _context.Vendors.CountAsync(); } } }