using Microsoft.EntityFrameworkCore; using vendorApproverBackend.Data; using vendorApproverBackend.Entities; using vendorApproverBackend.Enums; using vendorApproverBackend.Requests; using vendorApproverBackend.Responses; using vendorApproverBackend.Interfaces; namespace vendorApproverBackend.Services { public class VisitRequestService { private readonly DataContext _context; private readonly INotificationService _notificationService; private readonly ILogger _logger; private readonly UserService _userService; private readonly IEmailService _emailService; public VisitRequestService(DataContext context, INotificationService notificationService , IEmailService emailService, ILogger logger, UserService userService) { _context = context; _notificationService = notificationService; _emailService = emailService; _userService = userService; _logger = logger; } public async Task SaveAsync(VisitRequest visitRequest) { _context.VisitRequests.Add(visitRequest); await _context.SaveChangesAsync(); return visitRequest; } public async Task UpdateAsync(VisitRequest visitRequest) { _context.VisitRequests.Update(visitRequest); await _context.SaveChangesAsync(); return visitRequest; } public async Task FindByIdAsync(long id) { return await _context.VisitRequests.FindAsync(id); } public async Task FindByPrequalificationRequestIdAsync(long prequalificationRequestId) { var request = await _context.PrequalificationRequests .Include(p => p.VisitRequest) .FirstOrDefaultAsync(p => p.Id == prequalificationRequestId); return request?.VisitRequest; } public async Task AddNewVisitRequestAsync( long prequalificationRequestId, AddVisitRequest dto, long currentUserId) { var prequalificationRequest = await _context.PrequalificationRequests .Include(p => p.Vendor) .ThenInclude(v => v.User) .FirstOrDefaultAsync(p => p.Id == prequalificationRequestId); if (prequalificationRequest == null) { throw new Exception($"PrequalificationRequest with ID {prequalificationRequestId} not found."); } if (prequalificationRequest.VisitRequest != null) { throw new InvalidOperationException( "This PrequalificationRequest already has an associated VisitRequest."); } var newVisitRequest = new VisitRequest { Location = dto.Location, ContactPerson = dto.ContactPerson, VisitPlanId = dto.VisitPlanId, VisitPurpose = dto.VisitPurpose, ContactPhone = dto.ContactPhone, Reason = dto.Reason, Status = VisitRequestStatus.PENDING }; if (dto.StartDate.HasValue) { newVisitRequest.StartDate = dto.StartDate.Value.ToUniversalTime(); } if (dto.EndDate.HasValue) { newVisitRequest.EndDate = dto.EndDate.Value.ToUniversalTime(); } await SaveAsync(newVisitRequest); prequalificationRequest.VisitRequest = newVisitRequest; prequalificationRequest.Status = RequestStatus.FACTORY_VISIT_REQUESTED; await _context.SaveChangesAsync(); // 🔔 Notification block try { if (prequalificationRequest.Vendor?.User != null) { long vendorUserId = prequalificationRequest.Vendor.User.Id; var usersToNotify = new List { vendorUserId }; usersToNotify = usersToNotify.Distinct().ToList(); if (usersToNotify.Any()) { var notification = new NotificationModel { SenderId = currentUserId, Subject = "Factory visit plan request", Content = "Congratulations, you have passed the first stage. Please upload the factory visit plan.", Action = Action.Pq_FACTORY_VISIT_REQUEST, ActionId = prequalificationRequest.Id, LogUrl = "", Status = states.WAITING }; await _notificationService .SendNotificationToListOfUsers(usersToNotify, notification); _logger.LogInformation( "Sent PQ visit request notification to vendor user {VendorUserId}", vendorUserId); } } else { _logger.LogWarning( "Vendor or Vendor.User is null for PQ request {RequestId}", prequalificationRequest.Id); } try { if (prequalificationRequest.Vendor?.User != null) { // 1. Send Internal Dashboard Notification var vendorUser = prequalificationRequest.Vendor.User; await _notificationService.SendNotificationToListOfUsers( new List { vendorUser.Id }, new NotificationModel { /* ... your config ... */ } ); // 2. Send Professional Email to Vendor await _emailService.SendFactoryVisitVendorActionAsync(vendorUser.Email, vendorUser); // 3. Notify All Admins var adminEmails = await _userService.GetAdminEmailsAsync(); string representative = prequalificationRequest.Vendor.RepresentativeName ?? "A Vendor"; // We run these in parallel to improve performance var emailTasks = adminEmails.Select(adminEmail => _emailService.SendFactoryVisitAdminAlertAsync(adminEmail, representative, newVisitRequest) ); await Task.WhenAll(emailTasks); _logger.LogInformation("Factory Visit communications dispatched successfully for PQ {Id}", prequalificationRequest.Id); } } catch (Exception ex) { _logger.LogError(ex, "Mailing system failed for PQ {Id}, but request was saved.", prequalificationRequest.Id); } } catch (Exception ex) { _logger.LogError( ex, "Failed to send assignment notification for PQ request {RequestId}", prequalificationRequest.Id); } return newVisitRequest; } public async Task UpdateVisitRequestAsync(long visitRequestId, UpdateVisitRequest dto) { var existingVisitRequest = await _context.VisitRequests .FirstOrDefaultAsync(v => v.Id == visitRequestId); if (existingVisitRequest == null) return null; if (dto.StartDate.HasValue) { existingVisitRequest.StartDate = dto.StartDate.Value.ToUniversalTime(); } if (dto.EndDate.HasValue) { existingVisitRequest.EndDate = dto.EndDate.Value.ToUniversalTime(); } if (!string.IsNullOrEmpty(dto.Location)) { existingVisitRequest.Location = dto.Location; } if (!string.IsNullOrEmpty(dto.ContactPerson)) { existingVisitRequest.ContactPerson = dto.ContactPerson; } if (dto.VisitPlanId.HasValue) { existingVisitRequest.VisitPlanId = dto.VisitPlanId; } if (!string.IsNullOrEmpty(dto.Reason)) { existingVisitRequest.Reason = dto.Reason; } if (!string.IsNullOrEmpty(dto.VisitPurpose)) { existingVisitRequest.VisitPurpose = dto.VisitPurpose; } if (!string.IsNullOrEmpty(dto.ContactPhone)) { existingVisitRequest.ContactPhone = dto.ContactPhone; } _context.VisitRequests.Update(existingVisitRequest); var pq = await _context.PrequalificationRequests .Include(p => p.VisitRequest) .FirstOrDefaultAsync(p => p.VisitRequest != null && p.VisitRequest.Id == visitRequestId); if (pq != null) { pq.Status = RequestStatus.FACTORY_VISIT_PROPOSED; _context.PrequalificationRequests.Update(pq); } await _context.SaveChangesAsync(); return existingVisitRequest; } /* public async Task HandleVisitRequestActionAsync(long visitRequestId, VisitRequestActionRequest dto) { var visitRequest = await FindByIdAsync(visitRequestId); if (visitRequest == null) { return null; } var pq = await _context.PrequalificationRequests .Include(p => p.VisitRequest) .FirstOrDefaultAsync(p => p.VisitRequest != null && p.VisitRequest.Id == visitRequestId); if (dto.Status == VisitActionStatus.Reject) { visitRequest.Status = VisitRequestStatus.REJECTED; if (!string.IsNullOrWhiteSpace(dto.RejectionReason)) { visitRequest.Reason = dto.RejectionReason; } if (pq != null) { pq.Status = RequestStatus.FACTORY_VISIT_REQUESTED; _context.PrequalificationRequests.Update(pq); } await UpdateAsync(visitRequest); } else if (dto.Status == VisitActionStatus.Accept) { var prequalRequest = await _context.PrequalificationRequests .FirstOrDefaultAsync(p => p.VisitRequest.Id == visitRequestId); if (prequalRequest == null) { throw new Exception($"PrequalificationRequest linked to VisitRequest ID {visitRequestId} not found."); } visitRequest.Status = VisitRequestStatus.ACCEPTED; await UpdateAsync(visitRequest); if (pq != null) { pq.Status = RequestStatus.INSPECTION; _context.PrequalificationRequests.Update(pq); } await _context.SaveChangesAsync(); } return visitRequest; } */ public async Task HandleVisitRequestActionAsync(long visitRequestId, VisitRequestActionRequest dto) { var visitRequest = await FindByIdAsync(visitRequestId); if (visitRequest == null) return null; // Load the PQ request with Vendor and User data for mailing var pq = await _context.PrequalificationRequests .Include(p => p.Vendor) .ThenInclude(v => v.User) .Include(p => p.VisitRequest) .FirstOrDefaultAsync(p => p.VisitRequest != null && p.VisitRequest.Id == visitRequestId); if (pq == null) throw new Exception("PrequalificationRequest not found."); var vendorUser = pq.Vendor?.User; if (dto.Status == VisitActionStatus.Reject) { visitRequest.Status = VisitRequestStatus.REJECTED; visitRequest.Reason = dto.RejectionReason; pq.Status = RequestStatus.FACTORY_VISIT_REQUESTED; // Revert status so they can try again await UpdateAsync(visitRequest); _context.PrequalificationRequests.Update(pq); await _context.SaveChangesAsync(); // 📧 Send Rejection Email if (vendorUser != null) { await _emailService.SendVisitRequestRejectedAsync(vendorUser.Email, vendorUser, dto.RejectionReason); } } else if (dto.Status == VisitActionStatus.Accept) { visitRequest.Status = VisitRequestStatus.ACCEPTED; pq.Status = RequestStatus.INSPECTION; await UpdateAsync(visitRequest); _context.PrequalificationRequests.Update(pq); await _context.SaveChangesAsync(); // 📧 Send Acceptance Email if (vendorUser != null) { await _emailService.SendVisitRequestAcceptedAsync(vendorUser.Email, vendorUser, visitRequest); } } return visitRequest; } public async Task> GetVisitRequestsByCriteriaAsync( int page, int size, long? requestId = null, RequestStatus? status = null) { var baseQuery = _context.PrequalificationRequests .Where(p => p.VisitRequest != null) .AsQueryable(); if (requestId.HasValue && requestId.Value > 0) { baseQuery = baseQuery.Where(p => p.Id == requestId.Value); } if (status.HasValue) { baseQuery = baseQuery.Where(p => p.Status == status.Value); } var visitRequestQuery = baseQuery .Select(p => p.VisitRequest) .OrderByDescending(vr => vr.Timestamp); var totalCount = await visitRequestQuery.CountAsync(); var items = await visitRequestQuery .Skip(page * size) .Take(size) .ToListAsync(); return new PagedResult(items, totalCount, page, size); } } }