using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Activities; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Mapper; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace Marco.Pms.Services.Controllers { [Route("api/[controller]")] [ApiController] [Authorize] public class ImageController : ControllerBase { private readonly ApplicationDbContext _context; private readonly S3UploadService _s3Service; private readonly UserHelper _userHelper; private readonly ILoggingService _logger; private readonly PermissionServices _permission; private readonly Guid tenantId; public ImageController(ApplicationDbContext context, S3UploadService s3Service, UserHelper userHelper, ILoggingService logger, PermissionServices permission) { _context = context; _s3Service = s3Service; _userHelper = userHelper; _logger = logger; tenantId = userHelper.GetTenantId(); _permission = permission; } [HttpGet("images/{projectId}")] public async Task GetImageList(Guid projectId) { _logger.LogInfo("GetImageList called for ProjectId: {ProjectId}", projectId); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Step 1: Validate project existence var isProjectExist = await _context.Projects.AnyAsync(p => p.Id == projectId && p.TenantId == tenantId); if (!isProjectExist) { _logger.LogWarning("Project not found for ProjectId: {ProjectId}", projectId); return BadRequest(ApiResponse.ErrorResponse("Project not found", "Project not found in database", 400)); } // Step 2: Check permission var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.ToString()); if (!hasPermission) { _logger.LogWarning("No access to ProjectId: {ProjectId} for EmployeeId: {EmployeeId}", projectId, loggedInEmployee.Id); return StatusCode(403, ApiResponse.ErrorResponse("You don't have access", "You don't have access", 403)); } // Step 3: Fetch building > floor > work area > work item hierarchy var buildings = await _context.Buildings .Where(b => b.ProjectId == projectId) .Select(b => new { b.Id, b.Name }) .ToListAsync(); var buildingIds = buildings.Select(b => b.Id).ToList(); var floors = await _context.Floor .Where(f => buildingIds.Contains(f.BuildingId)) .Select(f => new { f.Id, f.BuildingId, f.FloorName }) .ToListAsync(); var floorIds = floors.Select(f => f.Id).ToList(); var workAreas = await _context.WorkAreas .Where(wa => floorIds.Contains(wa.FloorId)) .Select(wa => new { wa.Id, wa.FloorId, wa.AreaName }) .ToListAsync(); var workAreaIds = workAreas.Select(wa => wa.Id).ToList(); var workItems = await _context.WorkItems .Include(wi => wi.ActivityMaster) .Include(wi => wi.WorkCategoryMaster) .Where(wi => workAreaIds.Contains(wi.WorkAreaId)) .Select(wi => new { wi.Id, wi.WorkAreaId, wi.ActivityMaster, wi.WorkCategoryMaster }) .ToListAsync(); var workItemIds = workItems.Select(wi => wi.Id).ToList(); // Step 4: Fetch task and comment data var tasks = await _context.TaskAllocations .Include(t => t.ReportedBy) .Where(t => workItemIds.Contains(t.WorkItemId)) .ToListAsync(); var taskIds = tasks.Select(t => t.Id).ToList(); var comments = await _context.TaskComments .Include(c => c.Employee) .Where(c => taskIds.Contains(c.TaskAllocationId)) .ToListAsync(); var commentIds = comments.Select(c => c.Id).ToList(); // Step 5: Fetch attachments and related documents var attachments = await _context.TaskAttachments .Where(ta => taskIds.Contains(ta.ReferenceId) || commentIds.Contains(ta.ReferenceId)) .ToListAsync(); var documentIds = attachments.Select(ta => ta.DocumentId).ToList(); var documents = await _context.Documents .Include(d => d.UploadedBy) .Where(d => documentIds.Contains(d.Id)) .ToListAsync(); // Step 6: Prepare view models var documentVM = documents .Select(d => { var referenceId = attachments .Where(ta => ta.DocumentId == d.Id) .Select(ta => ta.ReferenceId) .FirstOrDefault(); var task = tasks.FirstOrDefault(t => t.Id == referenceId); var comment = comments.FirstOrDefault(c => c.Id == referenceId); string source = ""; Employee? uploadedBy = null; if (task != null) { uploadedBy = task.ReportedBy; source = "Report"; } else if (comment != null) { task = tasks.FirstOrDefault(t => t.Id == comment.TaskAllocationId); uploadedBy = comment.Employee; source = "Comment"; } var workItem = workItems.FirstOrDefault(wi => wi.Id == task?.WorkItemId); var workArea = workAreas.FirstOrDefault(wa => wa.Id == workItem?.WorkAreaId); var floor = floors.FirstOrDefault(f => f.Id == workArea?.FloorId); var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId); return new { Id = d.Id, BatchId = d.BatchId, thumbnailUrl = d.ThumbS3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.ThumbS3Key) : (d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null), ImageUrl = d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null, UploadedBy = d.UploadedBy?.ToBasicEmployeeVMFromEmployee() ?? uploadedBy?.ToBasicEmployeeVMFromEmployee(), UploadedAt = d.UploadedAt, Source = source, ProjectId = projectId, BuildingId = building?.Id, BuildingName = building?.Name, FloorIds = floor?.Id, FloorName = floor?.FloorName, WorkAreaId = workArea?.Id, WorkAreaName = workArea?.AreaName, TaskId = task?.Id, ActivityName = workItem?.ActivityMaster?.ActivityName, WorkCategoryId = workItem?.WorkCategoryMaster?.Id, WorkCategoryName = workItem?.WorkCategoryMaster?.Name, CommentId = comment?.Id, Comment = comment?.Comment }; }).ToList(); _logger.LogInfo("Image list fetched for ProjectId: {ProjectId}. Total documents: {Count}", projectId, documentVM.Count); return Ok(ApiResponse.SuccessResponse(documentVM, $"{documentVM.Count} image records fetched successfully", 200)); } [HttpGet("batch/{batchId}")] public async Task GetImagesByBatch(Guid batchId) { _logger.LogInfo("GetImagesByBatch called for BatchId: {BatchId}", batchId); // Step 1: Get the logged-in employee var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Step 2: Retrieve all documents in the batch var documents = await _context.Documents .Include(d => d.UploadedBy) .Where(d => d.BatchId == batchId) .ToListAsync(); if (!documents.Any()) { _logger.LogWarning("No documents found for BatchId: {BatchId}", batchId); return NotFound(ApiResponse.ErrorResponse("No images found", "No images associated with this batch", 404)); } var documentIds = documents.Select(d => d.Id).ToList(); // Step 3: Get task/comment reference IDs linked to these documents var referenceIds = await _context.TaskAttachments .Where(ta => documentIds.Contains(ta.DocumentId)) .Select(ta => ta.ReferenceId) .Distinct() .ToListAsync(); // Step 4: Try to identify the source of the attachment (task or comment) var task = await _context.TaskAllocations .Include(t => t.ReportedBy) .FirstOrDefaultAsync(t => referenceIds.Contains(t.Id)); TaskComment? comment = null; WorkItem? workItem = null; Employee? uploadedBy = null; string source = ""; if (task != null) { uploadedBy = task.ReportedBy; workItem = await _context.WorkItems .Include(wi => wi.ActivityMaster) .Include(wi => wi.WorkCategoryMaster) .FirstOrDefaultAsync(wi => wi.Id == task.WorkItemId); source = "Report"; } else { comment = await _context.TaskComments .Include(tc => tc.TaskAllocation) .Include(tc => tc.Employee) .FirstOrDefaultAsync(tc => referenceIds.Contains(tc.Id)); var workItemId = comment?.TaskAllocation?.WorkItemId; uploadedBy = comment?.Employee; workItem = await _context.WorkItems .Include(wi => wi.ActivityMaster) .Include(wi => wi.WorkCategoryMaster) .FirstOrDefaultAsync(wi => wi.Id == workItemId); source = "Comment"; } // Step 5: Traverse up to building level var workAreaId = workItem?.WorkAreaId; var workArea = await _context.WorkAreas .Include(wa => wa.Floor) .FirstOrDefaultAsync(wa => wa.Id == workAreaId); var buildingId = workArea?.Floor?.BuildingId; var building = await _context.Buildings .FirstOrDefaultAsync(b => b.Id == buildingId); // Step 6: Construct the response var response = documents.Select(d => new { Id = d.Id, BatchId = d.BatchId, thumbnailUrl = d.ThumbS3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.ThumbS3Key) : (d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null), ImageUrl = d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null, UploadedBy = d.UploadedBy?.ToBasicEmployeeVMFromEmployee() ?? uploadedBy?.ToBasicEmployeeVMFromEmployee(), UploadedAt = d.UploadedAt, Source = source, ProjectId = building?.ProjectId, BuildingId = building?.Id, BuildingName = building?.Name, FloorIds = workArea?.Floor?.Id, FloorName = workArea?.Floor?.FloorName, WorkAreaId = workArea?.Id, WorkAreaName = workArea?.AreaName, TaskId = task?.Id, ActivityName = workItem?.ActivityMaster?.ActivityName, WorkCategoryId = workItem?.WorkCategoryMaster?.Id, WorkCategoryName = workItem?.WorkCategoryMaster?.Name, CommentId = comment?.Id, Comment = comment?.Comment }).ToList(); _logger.LogInfo("Fetched {Count} image(s) for BatchId: {BatchId}", response.Count, batchId); return Ok(ApiResponse.SuccessResponse(response, "Images for provided batchId fetched successfully", 200)); } [HttpGet("{documentId}")] public async Task GetImage(Guid documentId) { // Log the start of the image fetch process _logger.LogInfo("GetImage called for DocumentId: {DocumentId}", documentId); // Step 1: Get the currently logged-in employee (for future use like permission checks or auditing) var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Step 2: Fetch the document from the database based on the provided ID var document = await _context.Documents.FirstOrDefaultAsync(d => d.Id == documentId); // Step 3: If document doesn't exist, return a 400 Bad Request response if (document == null) { _logger.LogWarning("Document not found for DocumentId: {DocumentId}", documentId); return BadRequest(ApiResponse.ErrorResponse("Document not found", "Document not found", 400)); } // Step 4: Generate pre-signed URLs for thumbnail and full image (if keys exist) string? thumbnailUrl = document.ThumbS3Key != null ? _s3Service.GeneratePreSignedUrlAsync(document.ThumbS3Key) : null; string? imageUrl = document.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(document.S3Key) : null; // Step 5: Prepare the response object var response = new { ThumbnailUrl = thumbnailUrl, ImageUrl = imageUrl }; // Step 6: Log successful fetch and return the result _logger.LogInfo("Image fetched successfully for DocumentId: {DocumentId}", documentId); return Ok(ApiResponse.SuccessResponse(response, "Image fetched successfully", 200)); } } }