using System.Data; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Dtos.Mail; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Mail; using Marco.Pms.Model.Utilities; using Marco.Pms.Services.Helpers; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using MongoDB.Driver; namespace Marco.Pms.Services.Controllers { [Route("api/[controller]")] [ApiController] [Authorize] public class ReportController : ControllerBase { private readonly ApplicationDbContext _context; private readonly IEmailSender _emailSender; private readonly ILoggingService _logger; private readonly UserHelper _userHelper; private readonly IWebHostEnvironment _env; private readonly ReportHelper _reportHelper; public ReportController(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, UserHelper userHelper, IWebHostEnvironment env, ReportHelper reportHelper) { _context = context; _emailSender = emailSender; _logger = logger; _userHelper = userHelper; _env = env; _reportHelper = reportHelper; } [HttpPost("set-mail")] public async Task AddMailDetails([FromBody] MailDetailsDto mailDetailsDto) { Guid tenantId = _userHelper.GetTenantId(); MailDetails mailDetails = new MailDetails { ProjectId = mailDetailsDto.ProjectId, Recipient = mailDetailsDto.Recipient, Schedule = mailDetailsDto.Schedule, MailListId = mailDetailsDto.MailListId, TenantId = tenantId }; _context.MailDetails.Add(mailDetails); await _context.SaveChangesAsync(); return Ok("Success"); } [HttpPost("mail-template")] public async Task AddMailTemplate([FromBody] MailTemeplateDto mailTemeplateDto) { Guid tenantId = _userHelper.GetTenantId(); if (string.IsNullOrWhiteSpace(mailTemeplateDto.Body) && string.IsNullOrWhiteSpace(mailTemeplateDto.Title)) { _logger.LogWarning("User tries to set email template but send invalid data"); return BadRequest(ApiResponse.ErrorResponse("Provided Invalid data", "Provided Invalid data", 400)); } var existngTemalate = await _context.MailingList.FirstOrDefaultAsync(t => t.Title.ToLower() == mailTemeplateDto.Title.ToLower()); if (existngTemalate != null) { _logger.LogWarning("User tries to set email template, but title already existed in database"); return BadRequest(ApiResponse.ErrorResponse("Email title is already existed", "Email title is already existed", 400)); } MailingList mailingList = new MailingList { Title = mailTemeplateDto.Title, Body = mailTemeplateDto.Body, Subject = mailTemeplateDto.Subject, Keywords = mailTemeplateDto.Keywords, TenantId = tenantId }; _context.MailingList.Add(mailingList); await _context.SaveChangesAsync(); return Ok("Success"); } [HttpGet("project-statistics")] public async Task SendProjectReport() { Guid tenantId = _userHelper.GetTenantId(); // Use AsNoTracking() for read-only queries to improve performance List mailDetails = await _context.MailDetails .AsNoTracking() .Include(m => m.MailBody) .Where(m => m.TenantId == tenantId) .ToListAsync(); int successCount = 0; int notFoundCount = 0; int invalidIdCount = 0; var groupedMails = mailDetails .GroupBy(m => new { m.ProjectId, m.MailListId }) .Select(g => new { ProjectId = g.Key.ProjectId, MailListId = g.Key.MailListId, Recipients = g.Select(m => m.Recipient).Distinct().ToList(), MailBody = g.FirstOrDefault()?.MailBody?.Body ?? "", Subject = g.FirstOrDefault()?.MailBody?.Subject ?? string.Empty, }) .ToList(); var semaphore = new SemaphoreSlim(1); // Using Task.WhenAll to send reports concurrently for better performance var sendTasks = groupedMails.Select(async mailDetail => { await semaphore.WaitAsync(); try { var response = await GetProjectStatistics(mailDetail.ProjectId, mailDetail.Recipients, mailDetail.MailBody, mailDetail.Subject, tenantId); if (response.StatusCode == 200) Interlocked.Increment(ref successCount); else if (response.StatusCode == 404) Interlocked.Increment(ref notFoundCount); else if (response.StatusCode == 400) Interlocked.Increment(ref invalidIdCount); } finally { semaphore.Release(); } }).ToList(); await Task.WhenAll(sendTasks); //var response = await GetProjectStatistics(Guid.Parse("2618eb89-2823-11f0-9d9e-bc241163f504"), "ashutosh.nehete@marcoaiot.com", tenantId); _logger.LogInfo( "Emails of project reports sent for tenant {TenantId}. Successfully sent: {SuccessCount}, Projects not found: {NotFoundCount}, Invalid IDs: {InvalidIdsCount}", tenantId, successCount, notFoundCount, invalidIdCount); return Ok(ApiResponse.SuccessResponse( new { }, $"Reports sent successfully: {successCount}. Projects not found: {notFoundCount}. Invalid IDs: {invalidIdCount}.", 200)); } /// /// Retrieves project statistics for a given project ID and sends an email report. /// /// The ID of the project. /// The email address of the recipient. /// An ApiResponse indicating the success or failure of retrieving statistics and sending the email. private async Task> GetProjectStatistics(Guid projectId, List recipientEmails, string body, string subject, Guid tenantId) { if (projectId == Guid.Empty) { _logger.LogError("Provided empty project ID while fetching project report."); return ApiResponse.ErrorResponse("Provided empty Project ID.", "Provided empty Project ID.", 400); } var statisticReport = await _reportHelper.GetDailyProjectReport(projectId, tenantId); if (statisticReport == null) { _logger.LogWarning("User attempted to fetch project progress for project ID {ProjectId} but not found.", projectId); return ApiResponse.ErrorResponse("Project not found.", "Project not found.", 404); } // Send Email var emailBody = await _emailSender.SendProjectStatisticsEmail(recipientEmails, body, subject, statisticReport); var employee = await _context.Employees.FirstOrDefaultAsync(e => e.Email != null && recipientEmails.Contains(e.Email)) ?? new Employee(); List mailLogs = new List(); foreach (var recipientEmail in recipientEmails) { mailLogs.Add( new MailLog { ProjectId = projectId, EmailId = recipientEmail, Body = emailBody, EmployeeId = employee.Id, TimeStamp = DateTime.UtcNow, TenantId = tenantId }); } _context.MailLogs.AddRange(mailLogs); await _context.SaveChangesAsync(); return ApiResponse.SuccessResponse(statisticReport, "Email sent successfully", 200); } } }