using Microsoft.EntityFrameworkCore; using vendorApproverBackend.Data; using vendorApproverBackend.Entities; using vendorApproverBackend.Enums; using vendorApproverBackend.Responses; using vendorApproverBackend.Requests; using vendorApproverBackend.Interfaces; namespace vendorApproverBackend.Services { public class VendorRequestLetterService { private readonly DataContext _context; private readonly UserService _userService; private readonly IEmailService _emailService; private readonly INotificationService _notificationService; private readonly ILogger _logger; public VendorRequestLetterService(DataContext context, IEmailService emailService, UserService userService, INotificationService notificationService, ILogger logger) { _context = context; _emailService = emailService; _userService = userService; _notificationService = notificationService; _logger = logger; } public async Task AddLetter(VendorRequestLetterCreate dto, long vendorId, long currentUserId) { var letter = new VendorRequestLetter { VendorId = vendorId, Status = LetterRequestStatus.DRAFT, SubmissionDate = DateTime.UtcNow, FullName = dto.FullName, ShortName = dto.ShortName, DateEstablished = dto.DateEstablished, OriginCountry = dto.OriginCountry, VendorGpsCoordinates = dto.VendorGpsCoordinates, VendorWebsite = dto.VendorWebsite, OfficeLocation = dto.OfficeLocation, GccCountryName = dto.GccCountryName, OtherCountryName = dto.OtherCountryName, OmanStreet = dto.OmanStreet, OmanPoBox = dto.OmanPoBox, OmanTel = dto.OmanTel, OmanFax = dto.OmanFax, HqStreet = dto.HqStreet, HqPoBox = dto.HqPoBox, HqTel = dto.HqTel, HqFax = dto.HqFax, HqEmail = dto.HqEmail, FactoryStreet = dto.FactoryStreet, FactoryPoBox = dto.FactoryPoBox, FactoryTel = dto.FactoryTel, FactoryFax = dto.FactoryFax, FactoryEmail = dto.FactoryEmail, SalesStreet = dto.SalesStreet, SalesPoBox = dto.SalesPoBox, SalesTel = dto.SalesTel, SalesFax = dto.SalesFax, SalesEmail = dto.SalesEmail, IsBranchInOman = dto.IsBranchInOman, AgentNames = dto.AgentNames, CommercialRegNumber = dto.CommercialRegNumber, AgentContactPerson = dto.AgentContactPerson, AgentDesignation = dto.AgentDesignation, RegisteredHeadOffice = dto.RegisteredHeadOffice, GpsCoordinates = dto.GpsCoordinates, AgentTel = dto.AgentTel, AgentFax = dto.AgentFax, AgentEmail = dto.AgentEmail, AgentWebsite = dto.AgentWebsite, OfficialAgencySubmitted = dto.OfficialAgencySubmitted, AuthorizedPersonsSubmitted = dto.AuthorizedPersonsSubmitted, LocalOwners = dto.LocalOwners ?? new(), BoardOfDirectors = dto.BoardOfDirectors ?? new(), ContactPersons = dto.ContactPersons ?? new(), AuthorizedSignatories = dto.AuthorizedSignatories ?? new(), CertificatesAndLicenses = dto.CertificatesAndLicenses, AssociatedCompanies = dto.AssociatedCompanies ?? new(), LetterProducts = dto.LetterProducts?.Select(p => new LetterProducts { Category = p.Category, Subcategory = p.Subcategory, ProductName = p.ProductName, ProductCity = p.ProductCity, ProductCountry = p.ProductCountry, ProductType = p.ProductType, ProductDesc = p.ProductDesc, Status = LetterProductStatus.PENDING }).ToList() ?? new List() }; _context.VendorRequestLetters.Add(letter); await _context.SaveChangesAsync(); // Notify admins about the new request letter try { var vendor = await _context.Vendors.AsNoTracking().FirstOrDefaultAsync(v => v.Id == vendorId); var vendorName = vendor?.RepresentativeName ?? "A Vendor"; var adminIds = await _userService.GetAdminUserIdsAsync(); if (adminIds.Any()) { var notification = new NotificationModel { SenderId = currentUserId, Subject = "New Vendor Request Letter", Content = $"{vendorName} submitted a new vendor request letter (#{letter.Id}).", Action = Action.NEW_VENDOR_REQUEST_LETTER, ActionId = letter.Id, LogUrl = "", Status = states.WAITING }; await _notificationService.SendNotificationToListOfUsers(adminIds, notification); } var adminEmails = await _userService.GetAdminEmailsAsync(); foreach (var adminEmail in adminEmails) { await _emailService.SendAdminNewLetterAlertAsync(adminEmail, vendorName, letter); } } catch (Exception ex) { _logger.LogError(ex, "Failed to send new-letter notifications for letter {LetterId}", letter.Id); } return letter; } public async Task UpdateLetter(long id, VendorRequestLetter request) { var existing = await _context.VendorRequestLetters .Include(l => l.LetterProducts) .FirstOrDefaultAsync(x => x.Id == id); if (existing == null) return null; // Manual mapping for reliability existing.FullName = request.FullName; existing.ShortName = request.ShortName; existing.OriginCountry = request.OriginCountry; existing.Status = request.Status; existing.LocalOwners = request.LocalOwners ?? existing.LocalOwners; existing.BoardOfDirectors = request.BoardOfDirectors ?? existing.BoardOfDirectors; existing.ContactPersons = request.ContactPersons ?? existing.ContactPersons; existing.AuthorizedSignatories = request.AuthorizedSignatories ?? existing.AuthorizedSignatories; existing.CertificatesAndLicenses = request.CertificatesAndLicenses ?? existing.CertificatesAndLicenses; existing.AssociatedCompanies = request.AssociatedCompanies ?? existing.AssociatedCompanies; await _context.SaveChangesAsync(); return existing; } public async Task ToggleManagerAssignment(long id, long currentUserId) { var letter = await _context.VendorRequestLetters .Include(l => l.Vendor) .FirstOrDefaultAsync(l => l.Id == id); if (letter == null) return null; bool wasAssigned = letter.AssignedToManager ?? false; letter.AssignedToManager = !wasAssigned; await _context.SaveChangesAsync(); // Notify managers only when the letter is newly assigned to them if (!wasAssigned && letter.AssignedToManager == true) { try { var managers = await _userService.FindUserByCriteriaList(role: RoleEnum.ROLE_MANAGER, active: true, deleted: false); if (managers.Any()) { var notification = new NotificationModel { SenderId = currentUserId, Subject = "Request Letter Assigned for Review", Content = $"Vendor request letter #{letter.Id} has been assigned to you for product review.", Action = Action.VENDOR_LETTER_ASSIGNED_TO_MANAGER, ActionId = letter.Id, LogUrl = "", Status = states.WAITING }; await _notificationService.SendNotificationToListOfUsers(managers.Select(m => m.Id), notification); foreach (var manager in managers) { await _emailService.SendLetterAssignedToManagerAsync(manager.Email, manager, letter); } } } catch (Exception ex) { _logger.LogError(ex, "Failed to send manager-assignment notifications for letter {LetterId}", letter.Id); } } return letter; } /* public async Task UpdateLetterStatus(long id, LetterRequestStatus status) { var letter = await _context.VendorRequestLetters.FindAsync(id); if (letter == null) return null; letter.Status = status; if (status == LetterRequestStatus.APPROVED) letter.ApprovalDate = DateTime.UtcNow; await _context.SaveChangesAsync(); return letter; } */ public async Task UpdateLetterStatus(long id, LetterRequestStatus status) { // Include necessary relations for mailing var letter = await _context.VendorRequestLetters .Include(l => l.LetterProducts) .Include(l => l.Vendor) .ThenInclude(v => v.User) .FirstOrDefaultAsync(l => l.Id == id); if (letter == null) return null; letter.Status = status; if (status == LetterRequestStatus.APPROVED) letter.ApprovalDate = DateTime.UtcNow; await _context.SaveChangesAsync(); // Trigger Mailing when status changes to SUBMITTED if (status == LetterRequestStatus.SUBMITTED) { try { var vendorUser = letter.Vendor?.User; var vendorName = letter.Vendor?.RepresentativeName ?? "A Vendor"; // 1. Notify Vendor if (vendorUser != null) { await _emailService.SendVendorLetterSubmittedAsync(vendorUser.Email, vendorUser, letter); } // 2. Notify Admins var adminEmails = await _userService.GetAdminEmailsAsync(); foreach (var adminEmail in adminEmails) { await _emailService.SendAdminNewLetterAlertAsync(adminEmail, vendorName, letter); } } catch (Exception ex) { // Log error but don't break the HTTP response } } return letter; } /* public async Task UpdateProductStatus(long id, LetterProductStatus status) { var product = await _context.LetterProducts.FindAsync(id); if (product == null) return null; product.Status = status; await _context.SaveChangesAsync(); return product; } */ public async Task UpdateProductStatus(long id, LetterProductStatus status, string? rejectionReason = null, long currentUserId = 0) { var product = await _context.LetterProducts .Include(p => p.VendorRequestLetter) .ThenInclude(l => l.Vendor) .ThenInclude(v => v.User) .FirstOrDefaultAsync(p => p.Id == id); if (product == null) return null; product.Status = status; // Logic: If status is a rejection, update the reason. if (status == LetterProductStatus.REJECTED || status == LetterProductStatus.REJECTED_BY_MANAGER || status == LetterProductStatus.REJECTED_BY_ADMIN) { product.RejectionReason = rejectionReason; } await _context.SaveChangesAsync(); var letter = product.VendorRequestLetter; try { if (status == LetterProductStatus.APPROVED_BY_MANAGER || status == LetterProductStatus.REJECTED_BY_MANAGER) { // Notify admins of the manager's decision bool approved = status == LetterProductStatus.APPROVED_BY_MANAGER; var adminIds = await _userService.GetAdminUserIdsAsync(); if (adminIds.Any()) { var notification = new NotificationModel { SenderId = currentUserId, Subject = approved ? "Product Approved by Manager" : "Product Rejected by Manager", Content = $"Manager has {(approved ? "approved" : "rejected")} product '{product.ProductName}' on request letter #{letter.Id}.", Action = Action.VENDOR_LETTER_PRODUCT_MANAGER_DECISION, ActionId = letter.Id, LogUrl = "", Status = states.WAITING }; await _notificationService.SendNotificationToListOfUsers(adminIds, notification); } var adminEmails = await _userService.GetAdminEmailsAsync(); foreach (var adminEmail in adminEmails) { await _emailService.SendAdminProductManagerDecisionAsync(adminEmail, product, letter, approved); } } else if (status == LetterProductStatus.APPROVED_BY_ADMIN || status == LetterProductStatus.REJECTED_BY_ADMIN) { // Notify the vendor of the admin's final decision bool approved = status == LetterProductStatus.APPROVED_BY_ADMIN; var vendorUser = letter?.Vendor?.User; if (vendorUser != null) { var notification = new NotificationModel { SenderId = currentUserId, Subject = approved ? "Product Approved" : "Product Rejected", Content = $"Your product '{product.ProductName}' has been {(approved ? "approved" : "rejected")} by the admin.", Action = Action.VENDOR_LETTER_PRODUCT_ADMIN_DECISION, ActionId = letter.Id, LogUrl = "", Status = states.WAITING }; await _notificationService.SendNotificationToUser(vendorUser.Id, notification); await _emailService.SendVendorProductAdminDecisionAsync(vendorUser.Email, vendorUser, product, letter, approved); } else { _logger.LogWarning( "Could not notify vendor for product {ProductId}: letter={LetterFound}, vendorId={VendorId}, vendor={VendorFound}, user={UserFound}", id, letter != null, letter?.VendorId, letter?.Vendor != null, letter?.Vendor?.User != null); } } } catch (Exception ex) { _logger.LogError(ex, "Failed to send product-status notifications for product {ProductId}", id); } return product; } public async Task DeleteLetter(long id) { var letter = await _context.VendorRequestLetters.FindAsync(id); if (letter != null) { _context.VendorRequestLetters.Remove(letter); await _context.SaveChangesAsync(); } } public async Task GetById(long id) { return await _context.VendorRequestLetters .Include(l => l.LetterProducts) .AsNoTracking() .FirstOrDefaultAsync(x => x.Id == id); } public async Task> GetMyLetters( int page, int size, long? vendorId, LetterRequestStatus? status, LetterProductStatus? productStatus, bool? assignedToManager = null) // Added to match controller needs { // Start with a clean query including related products var query = _context.VendorRequestLetters .Include(l => l.LetterProducts) .AsNoTracking() .AsQueryable(); // 1. Filter by Vendor (This is the primary filter for "My Letters") if (vendorId.HasValue) { query = query.Where(l => l.VendorId == vendorId.Value); } // 2. Filter by Status (Optional: DRAFT, SUBMITTED, etc.) // If 'status' is null, it will now include DRAFTs automatically if (status.HasValue) { query = query.Where(l => l.Status == status.Value); } // 3. Filter by Product Status inside the letter if (productStatus.HasValue) { query = query.Where(l => l.LetterProducts.Any(p => p.Status == productStatus.Value)); } // 4. Filter by Manager Assignment if (assignedToManager.HasValue) { query = query.Where(l => l.AssignedToManager == assignedToManager.Value); } // Calculate total count for pagination int total = await query.CountAsync(); // Fetch paginated results var items = await query .OrderByDescending(l => l.SubmissionDate) .Skip(page * size) .Take(size) .ToListAsync(); return new PagedResult(items, total, page, size); } public async Task> GetAll(int page, int size, long? vendorId, LetterRequestStatus? status, LetterProductStatus? productStatus, bool? assignedToManager) { var query = _context.VendorRequestLetters.Include(l => l.LetterProducts).AsNoTracking().AsQueryable(); query = query.Where(l => l.Status != LetterRequestStatus.DRAFT); if (vendorId.HasValue) query = query.Where(l => l.VendorId == vendorId.Value); if (status.HasValue) query = query.Where(l => l.Status == status.Value); if (assignedToManager.HasValue) query = query.Where(l => l.AssignedToManager == assignedToManager.Value); if (productStatus.HasValue) query = query.Where(l => l.LetterProducts.Any(p => p.Status == productStatus.Value)); int total = await query.CountAsync(); var items = await query.OrderByDescending(l => l.SubmissionDate).Skip(page * size).Take(size).ToListAsync(); return new PagedResult(items, total, page, size); } public async Task AddProductToLetter(long letterId, CreateLetterProductDto dto) { var product = new LetterProducts { VendorRequestLetterId = letterId, Category = dto.Category, Subcategory = dto.Subcategory, ProductName = dto.ProductName, ProductCity = dto.ProductCity, ProductCountry = dto.ProductCountry, ProductType = dto.ProductType, ProductDesc = dto.ProductDesc, Status = LetterProductStatus.PENDING }; _context.LetterProducts.Add(product); await _context.SaveChangesAsync(); return product; } public async Task UpdateProduct(long id, UpdateProductRequestLetter request) { var existing = await _context.LetterProducts.FindAsync(id); if (existing == null) return null; if (request.ProductName != null) existing.ProductName = request.ProductName; if (request.Category != null) existing.Category = request.Category; if (request.Subcategory != null) existing.Subcategory = request.Subcategory; if (request.ProductCity != null) existing.ProductCity = request.ProductCity; if (request.ProductCountry != null) existing.ProductCountry = request.ProductCountry; if (request.ProductType != null) existing.ProductType = request.ProductType; if (request.ProductDesc != null) existing.ProductDesc = request.ProductDesc; if (request.AdminComment != null) existing.AdminComment = request.AdminComment; if (request.ManagerComment != null) existing.ManagerComment = request.ManagerComment; await _context.SaveChangesAsync(); return existing; } public async Task RemoveProduct(long id) { var prod = await _context.LetterProducts.FindAsync(id); if (prod != null) { _context.LetterProducts.Remove(prod); await _context.SaveChangesAsync(); } } } }