using AutoMapper;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Helpers.Utility;
using Marco.Pms.Model.Collection;
using Marco.Pms.Model.DocumentManager;
using Marco.Pms.Model.Dtos.Collection;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.MongoDBModels.Utility;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Collection;
using Marco.Pms.Model.ViewModels.Projects;
using Marco.Pms.Services.Service;
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 CollectionController : ControllerBase
    {
        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
        private readonly IServiceScopeFactory _serviceScopeFactory;
        private readonly UserHelper _userHelper;
        private readonly S3UploadService _s3Service;
        private readonly IMapper _mapper;
        private readonly ILoggingService _logger;
        private readonly Guid tenantId;
        public CollectionController(IDbContextFactory<ApplicationDbContext> dbContextFactory,
            IServiceScopeFactory serviceScopeFactory,
            S3UploadService s3Service,
            UserHelper userhelper,
            ILoggingService logger,
            IMapper mapper)
        {
            _dbContextFactory = dbContextFactory;
            _serviceScopeFactory = serviceScopeFactory;
            _userHelper = userhelper;
            _s3Service = s3Service;
            _mapper = mapper;
            _logger = logger;
            tenantId = userhelper.GetTenantId();
        }

        #region =================================================================== Get Functions ===================================================================

        [HttpGet("invoice/list")]
        public async Task<IActionResult> GetInvoiceListAsync([FromQuery] Guid? projectId, [FromQuery] string? searchString, [FromQuery] DateTime? fromDate, [FromQuery] DateTime? toDate, [FromQuery] int pageSize = 20, [FromQuery] int pageNumber = 1
            , [FromQuery] bool isActive = true, [FromQuery] bool isPending = false)
        {
            _logger.LogInfo(
                "Fetching invoice list: Page {PageNumber}, Size {PageSize}, Active={IsActive}, PendingOnly={IsPending}, Search='{SearchString}', From={From}, To={To}",
                pageNumber, pageSize, isActive, isPending, searchString ?? "", fromDate?.Date ?? DateTime.MinValue, toDate?.Date ?? DateTime.MaxValue);

            // Get the currently logged-in employee
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Log starting permission checks
            _logger.LogInfo("Starting permission checks for EmployeeId: {EmployeeId}", loggedInEmployee.Id);

            // Initiate permission check tasks asynchronously
            var adminPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.CollectionAdmin, loggedInEmployee.Id);
            });

            var viewPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.ViewCollection, loggedInEmployee.Id);
            });

            var createPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.CreateCollection, loggedInEmployee.Id);
            });

            var editPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.EditCollection, loggedInEmployee.Id);
            });

            var addPaymentPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.AddPayment, loggedInEmployee.Id);
            });

            // Await all permission checks to complete concurrently
            await Task.WhenAll(adminPermissionTask, viewPermissionTask, createPermissionTask, editPermissionTask, addPaymentPermissionTask);

            // Capture permission results
            var hasAdminPermission = adminPermissionTask.Result;
            var hasViewPermission = viewPermissionTask.Result;
            var hasCreatePermission = createPermissionTask.Result;
            var hasEditPermission = editPermissionTask.Result;
            var hasAddPaymentPermission = addPaymentPermissionTask.Result;

            // Log permission results for audit
            _logger.LogInfo("Permission results for EmployeeId {EmployeeId}: Admin={Admin}, View={View}, Create={Create}, Edit={Edit}, Add Payment={AddPayment}",
                loggedInEmployee.Id, hasAdminPermission, hasViewPermission, hasCreatePermission, hasEditPermission, hasAddPaymentPermission);

            // Check if user has any relevant permission; if none, deny access
            if (!hasAdminPermission && !hasViewPermission && !hasCreatePermission && !hasEditPermission && !hasAddPaymentPermission)
            {
                _logger.LogWarning("Permission denied for EmployeeId {EmployeeId} - No collection-related permissions found.", loggedInEmployee.Id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse(
                    "Access Denied",
                    "User does not have permission to access collection data.",
                    403));
            }

            // Optionally log success or continue with further processing here
            _logger.LogInfo("Permission granted for EmployeeId {EmployeeId} - Proceeding with collection access.", loggedInEmployee.Id);

            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Build base query with required includes and no tracking
            var invoicesQuery = _context.Invoices
                .Include(i => i.Project)
                .Include(i => i.CreatedBy).ThenInclude(e => e!.JobRole)
                .Include(i => i.UpdatedBy).ThenInclude(e => e!.JobRole)
                .Where(i => i.IsActive == isActive && i.TenantId == tenantId)
                .AsNoTracking(); // Disable change tracking for read-only query

            // Apply date filter
            if (fromDate.HasValue && toDate.HasValue)
            {
                var fromDateUtc = fromDate.Value.Date;
                var toDateUtc = toDate.Value.Date.AddDays(1).AddTicks(-1); // End of day
                invoicesQuery = invoicesQuery.Where(i => i.InvoiceDate >= fromDateUtc && i.InvoiceDate <= toDateUtc);
                _logger.LogDebug("Applied date filter: {From} to {To}", fromDateUtc, toDateUtc);
            }

            // Apply search filter
            if (!string.IsNullOrWhiteSpace(searchString))
            {
                invoicesQuery = invoicesQuery.Where(i => i.Title.Contains(searchString) || i.InvoiceNumber.Contains(searchString));
                _logger.LogDebug("Applied search filter with term: {SearchString}", searchString);
            }

            // Apply project filter
            if (projectId.HasValue)
            {
                invoicesQuery = invoicesQuery.Where(i => i.ProjectId == projectId.Value);
                _logger.LogDebug("Applied project filter with term: {ProjectId}", projectId);
            }

            // Get total count before pagination
            var totalEntites = await invoicesQuery.CountAsync();
            _logger.LogDebug("Total matching invoices: {TotalCount}", totalEntites);

            // Apply sorting and pagination
            var invoices = await invoicesQuery
                .OrderByDescending(i => i.InvoiceDate)
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize)
                .ToListAsync();

            if (!invoices.Any())
            {
                _logger.LogInfo("No invoices found for the given criteria.");
                var emptyResponse = new
                {
                    CurrentPage = pageNumber,
                    TotalPages = 0,
                    TotalEntites = 0,
                    Data = new List<InvoiceListVM>()
                };
                return Ok(ApiResponse<object>.SuccessResponse(emptyResponse, "No invoices found"));
            }

            // Fetch all related payment data in a single query
            var invoiceIds = invoices.Select(i => i.Id).ToList();
            var paymentGroups = await _context.ReceivedInvoicePayments
                .AsNoTracking()
                .Where(rip => invoiceIds.Contains(rip.InvoiceId) && rip.TenantId == tenantId)
                .GroupBy(rip => rip.InvoiceId)
                .Select(g => new
                {
                    InvoiceId = g.Key,
                    PaidAmount = g.Sum(rip => rip.Amount)
                })
                .ToDictionaryAsync(x => x.InvoiceId, x => x.PaidAmount);

            _logger.LogDebug("Fetched payment data for {Count} invoices", paymentGroups.Count);

            // Map and calculate balance in memory
            var results = new List<InvoiceListVM>();
            foreach (var invoice in invoices)
            {
                var totalAmount = invoice.BasicAmount + invoice.TaxAmount;
                var paidAmount = paymentGroups.GetValueOrDefault(invoice.Id, 0);
                var balanceAmount = totalAmount - paidAmount;

                // Skip if filtering for pending invoices and balance is zero
                if (isPending && (balanceAmount <= 0 || invoice.MarkAsCompleted))
                    continue;

                var result = _mapper.Map<InvoiceListVM>(invoice);
                result.BalanceAmount = balanceAmount;
                results.Add(result);
            }

            var totalPages = (int)Math.Ceiling((double)totalEntites / pageSize);
            var response = new
            {
                CurrentPage = pageNumber,
                TotalPages = totalPages,
                TotalEntites = totalEntites,
                Data = results
            };

            _logger.LogInfo("Successfully returned {ResultCount} invoices out of {TotalCount} total", results.Count, totalEntites);
            return Ok(ApiResponse<object>.SuccessResponse(response, $"{results.Count} invoices fetched successfully"));
        }

        /// <summary>
        /// Retrieves complete details of a specific invoice including associated comments, attachments, and payments.
        /// </summary>
        /// <param name="id">The unique identifier of the invoice.</param>
        /// <returns>Returns invoice details with associated data or a NotFound/BadRequest response.</returns>

        [HttpGet("invoice/details/{id}")]
        public async Task<IActionResult> GetInvoiceDetailsAsync(Guid id)
        {
            _logger.LogInfo("Fetching details for InvoiceId: {InvoiceId}, TenantId: {TenantId}", id, tenantId);

            // Get the currently logged-in employee
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Log starting permission checks
            _logger.LogInfo("Starting permission checks for EmployeeId: {EmployeeId}", loggedInEmployee.Id);

            // Initiate permission check tasks asynchronously
            var adminPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.CollectionAdmin, loggedInEmployee.Id);
            });

            var viewPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.ViewCollection, loggedInEmployee.Id);
            });

            var createPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.CreateCollection, loggedInEmployee.Id);
            });

            var editPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.EditCollection, loggedInEmployee.Id);
            });

            var addPaymentPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.AddPayment, loggedInEmployee.Id);
            });

            // Await all permission checks to complete concurrently
            await Task.WhenAll(adminPermissionTask, viewPermissionTask, createPermissionTask, editPermissionTask, addPaymentPermissionTask);

            // Capture permission results
            var hasAdminPermission = adminPermissionTask.Result;
            var hasViewPermission = viewPermissionTask.Result;
            var hasCreatePermission = createPermissionTask.Result;
            var hasEditPermission = editPermissionTask.Result;
            var hasAddPaymentPermission = addPaymentPermissionTask.Result;

            // Log permission results for audit
            _logger.LogInfo("Permission results for EmployeeId {EmployeeId}: Admin={Admin}, View={View}, Create={Create}, Edit={Edit}, Add Payment={AddPayment}",
                loggedInEmployee.Id, hasAdminPermission, hasViewPermission, hasCreatePermission, hasEditPermission, hasAddPaymentPermission);

            // Check if user has any relevant permission; if none, deny access
            if (!hasAdminPermission && !hasViewPermission && !hasCreatePermission && !hasEditPermission && !hasAddPaymentPermission)
            {
                _logger.LogWarning("Permission denied for EmployeeId {EmployeeId} - No collection-related permissions found.", loggedInEmployee.Id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse(
                    "Access Denied",
                    "User does not have permission to access collection data.",
                    403));
            }

            // Optionally log success or continue with further processing here
            _logger.LogInfo("Permission granted for EmployeeId {EmployeeId} - Proceeding with collection access.", loggedInEmployee.Id);

            await using var context = await _dbContextFactory.CreateDbContextAsync();

            // Retrieve primary invoice details with related entities (project, created/updated by + roles)
            var invoice = await context.Invoices
                .Include(i => i.Project)
                .Include(i => i.CreatedBy).ThenInclude(e => e!.JobRole)
                .Include(i => i.UpdatedBy).ThenInclude(e => e!.JobRole)
                .AsNoTracking()
                .FirstOrDefaultAsync(i => i.Id == id && i.TenantId == tenantId);

            if (invoice == null)
            {
                _logger.LogWarning("Invoice not found. InvoiceId: {InvoiceId}, TenantId: {TenantId}", id, tenantId);
                return NotFound(ApiResponse<object>.ErrorResponse("Invoice not found", "The specified invoice does not exist.", 404));
            }

            _logger.LogInfo("Invoice {InvoiceId} found. Fetching related data...", id);

            // Parallelize loading of child collections using independent DbContext instances — efficient and thread-safe
            var commentsTask = LoadInvoiceCommentsAsync(id, tenantId);
            var attachmentsTask = LoadInvoiceAttachmentsAsync(id, tenantId);
            var paymentsTask = LoadReceivedInvoicePaymentsAsync(id, tenantId);

            await Task.WhenAll(commentsTask, attachmentsTask, paymentsTask);

            var comments = commentsTask.Result;
            var attachments = attachmentsTask.Result;
            var receivedInvoicePayments = paymentsTask.Result;

            // Map invoice to response view model
            var response = _mapper.Map<InvoiceDetailsVM>(invoice);

            // Populate related data
            if (comments.Any())
                response.Comments = _mapper.Map<List<InvoiceCommentVM>>(comments);

            if (attachments.Any())
            {
                response.Attachments = attachments.Select(a =>
                {
                    var result = _mapper.Map<InvoiceAttachmentVM>(a);
                    result.PreSignedUrl = _s3Service.GeneratePreSignedUrl(a.Document!.S3Key);
                    result.UploadedBy = _mapper.Map<BasicEmployeeVM>(a.Document.UploadedBy);
                    result.FileName = a.Document.FileName;
                    result.ContentType = a.Document.ContentType;
                    return result;
                }).ToList();
            }

            if (receivedInvoicePayments.Any())
                response.ReceivedInvoicePayments = _mapper.Map<List<ReceivedInvoicePaymentVM>>(receivedInvoicePayments);

            // Compute total paid and balance amounts
            double totalPaidAmount = receivedInvoicePayments.Sum(rip => rip.Amount);
            double totalAmount = invoice.BasicAmount + invoice.TaxAmount;
            response.BalanceAmount = totalAmount - totalPaidAmount;

            _logger.LogInfo("Invoice {InvoiceId} details fetched successfully: Total = {TotalAmount}, Paid = {PaidAmount}, Balance = {BalanceAmount}",
                id, totalAmount, totalPaidAmount, response.BalanceAmount);

            return Ok(ApiResponse<object>.SuccessResponse(response, "Invoice details fetched successfully", 200));
        }

        #endregion

        #region =================================================================== Post Functions ===================================================================

        [HttpPost("invoice/create")]
        public async Task<IActionResult> CreateInvoiceAsync([FromBody] InvoiceDto model)
        {
            // Get the currently logged-in employee
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Log starting permission checks
            _logger.LogInfo("Starting permission checks for EmployeeId: {EmployeeId}", loggedInEmployee.Id);

            // Initiate permission check tasks asynchronously
            var adminPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.CollectionAdmin, loggedInEmployee.Id);
            });

            var createPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.CreateCollection, loggedInEmployee.Id);
            });

            // Await all permission checks to complete concurrently
            await Task.WhenAll(adminPermissionTask, createPermissionTask);

            // Capture permission results
            var hasAdminPermission = adminPermissionTask.Result;
            var hasCreatePermission = createPermissionTask.Result;

            // Log permission results for audit
            _logger.LogInfo("Permission results for EmployeeId {EmployeeId}: Admin={Admin}, Create={Create}",
                loggedInEmployee.Id, hasAdminPermission, hasCreatePermission);

            // Check if user has any relevant permission; if none, deny access
            if (!hasAdminPermission && !hasCreatePermission)
            {
                _logger.LogWarning("Permission denied for EmployeeId {EmployeeId} - No collection-related permissions found.", loggedInEmployee.Id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse(
                    "Access Denied",
                    "User does not have permission to access collection data.",
                    403));
            }

            // Optionally log success or continue with further processing here
            _logger.LogInfo("Permission granted for EmployeeId {EmployeeId} - Proceeding with collection access.", loggedInEmployee.Id);

            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            _logger.LogInfo("Starting invoice creation for ProjectId: {ProjectId} by EmployeeId: {EmployeeId}",
                model.ProjectId, loggedInEmployee.Id);

            if (model.InvoiceNumber.Length > 17)
            {
                _logger.LogWarning("Invoice Number {InvoiceNumber} is greater than 17 charater",
                    model.InvoiceNumber);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Invoice Number cannot be greater than 17 charater",
                    "Invoice Number is greater than 17 charater", 400));
            }

            // Validate date sequence
            if (model.InvoiceDate.Date > DateTime.UtcNow.Date)
            {
                _logger.LogWarning("Invoice date {InvoiceDate} cannot be in the future.",
                    model.InvoiceDate);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Invoice date cannot be in the future",
                    "Invoice date cannot be in the future", 400));
            }
            if (model.InvoiceDate.Date > model.ClientSubmitedDate.Date)
            {
                _logger.LogWarning("Invoice date {InvoiceDate} is later than client submitted date {ClientSubmitedDate}",
                    model.InvoiceDate, model.ClientSubmitedDate);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Invoice date cannot be later than the client submitted date",
                    "Invoice date is later than client submitted date", 400));
            }
            if (model.ClientSubmitedDate.Date > DateTime.UtcNow.Date)
            {
                _logger.LogWarning("Client submited date {ClientSubmitedDate} cannot be in the future.",
                    model.InvoiceDate);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Client submited date cannot be in the future",
                    "Client submited date cannot be in the future", 400));
            }
            if (model.ClientSubmitedDate.Date > model.ExceptedPaymentDate.Date)
            {
                _logger.LogWarning("Client submitted date {ClientSubmitedDate} is later than expected payment date {ExpectedPaymentDate}",
                    model.ClientSubmitedDate, model.ExceptedPaymentDate);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Client submission date cannot be later than the expected payment date",
                    "Client submitted date is later than expected payment date", 400));
            }

            // Check for existing InvoiceNumber for this tenant before creating/updating to maintain uniqueness.
            var invoiceNumberExists = await _context.Invoices
                .AnyAsync(i => i.InvoiceNumber == model.InvoiceNumber && i.TenantId == tenantId);

            if (invoiceNumberExists)
            {
                // Log the conflict event with full context for audit/review.
                _logger.LogWarning(
                    "Invoice number conflict detected for InvoiceNumber: {InvoiceNumber} and TenantId: {TenantId}",
                    model.InvoiceNumber, tenantId);

                // Return HTTP 409 (Conflict) with a descriptive, actionable message.
                return StatusCode(409, ApiResponse<object>.ErrorResponse(
                    "Invoice number already exists",
                    $"The invoice number '{model.InvoiceNumber}' is already in use for this tenant. Please choose a unique invoice number.",
                    409));
            }

            // If E-InvoiceNumber is provided (optional), validate its uniqueness for this tenant.
            if (!string.IsNullOrWhiteSpace(model.EInvoiceNumber))
            {
                var eInvoiceNumberExists = await _context.Invoices
                    .AnyAsync(i => i.EInvoiceNumber == model.EInvoiceNumber && i.TenantId == tenantId);

                if (eInvoiceNumberExists)
                {
                    _logger.LogWarning(
                        "E-Invoice number conflict detected for EInvoiceNumber: {EInvoiceNumber} and TenantId: {TenantId}",
                        model.EInvoiceNumber, tenantId);

                    // Return HTTP 409 (Conflict) with a tailored message for E-Invoice.
                    return StatusCode(409, ApiResponse<object>.ErrorResponse(
                        "E-Invoice number already exists",
                        $"The E-Invoice number '{model.EInvoiceNumber}' is already assigned to another invoice for this tenant. Please provide a unique E-Invoice number.",
                        409));
                }
            }


            // Fetch project
            var project = await _context.Projects
                .FirstOrDefaultAsync(p => p.Id == model.ProjectId && p.TenantId == tenantId);
            if (project == null)
            {
                _logger.LogWarning("Project not found: ProjectId {ProjectId}, TenantId {TenantId}",
                    model.ProjectId, tenantId);
                return NotFound(ApiResponse<object>.ErrorResponse("Project not found", "Project not found", 404));
            }

            // Begin transaction scope with async flow support
            await using var transaction = await _context.Database.BeginTransactionAsync();
            var invoice = new Invoice();
            try
            {
                // Map and create invoice
                invoice = _mapper.Map<Invoice>(model);
                invoice.IsActive = true;
                invoice.MarkAsCompleted = false;
                invoice.CreatedAt = DateTime.UtcNow;
                invoice.CreatedById = loggedInEmployee.Id;
                invoice.TenantId = tenantId;

                _context.Invoices.Add(invoice);
                await _context.SaveChangesAsync(); // Save to generate invoice.Id

                // Handle attachments
                var documents = new List<Document>();
                var invoiceAttachments = new List<InvoiceAttachment>();
                if (model.Attachments?.Any() == true)
                {
                    var batchId = Guid.NewGuid();

                    foreach (var attachment in model.Attachments)
                    {
                        string base64 = attachment.Base64Data?.Split(',').LastOrDefault() ?? "";
                        if (string.IsNullOrWhiteSpace(base64))
                        {
                            _logger.LogWarning("Base64 data is missing for attachment {FileName}", attachment.FileName ?? "");
                            return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Image data missing", 400));
                        }

                        var fileType = _s3Service.GetContentTypeFromBase64(base64);
                        var fileName = _s3Service.GenerateFileName(fileType, tenantId, "invoice");
                        var objectKey = $"tenant-{tenantId}/Project/{model.ProjectId}/Invoice/{fileName}";

                        await _s3Service.UploadFileAsync(base64, fileType, objectKey);

                        var document = new Document
                        {
                            Id = Guid.NewGuid(),
                            BatchId = batchId,
                            UploadedById = loggedInEmployee.Id,
                            FileName = attachment.FileName ?? fileName,
                            ContentType = attachment.ContentType,
                            S3Key = objectKey,
                            FileSize = attachment.FileSize,
                            UploadedAt = DateTime.UtcNow,
                            TenantId = tenantId
                        };
                        documents.Add(document);

                        var invoiceAttachment = new InvoiceAttachment
                        {
                            InvoiceId = invoice.Id,
                            DocumentId = document.Id,
                            TenantId = tenantId
                        };
                        invoiceAttachments.Add(invoiceAttachment);
                    }

                    _context.Documents.AddRange(documents);
                    _context.InvoiceAttachments.AddRange(invoiceAttachments);
                    await _context.SaveChangesAsync(); // Save attachments and mappings
                }

                // Commit transaction
                await transaction.CommitAsync();
                _logger.LogInfo("Invoice {InvoiceId} created successfully with {AttachmentCount} attachments.",
                    invoice.Id, documents.Count);
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                _logger.LogError(ex, "Transaction rolled back during invoice creation for ProjectId {ProjectId}", model.ProjectId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse(
                    "Transaction failed: " + ex.Message,
                    "An error occurred while creating the invoice", 500));
            }

            // Build response
            var response = _mapper.Map<InvoiceListVM>(invoice);
            response.Project = _mapper.Map<BasicProjectVM>(project);
            response.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
            response.BalanceAmount = response.BasicAmount + response.TaxAmount;

            return StatusCode(201, ApiResponse<object>.SuccessResponse(response, "Invoice Created Successfully", 201));
        }

        /// <summary>
        /// Creates a new received invoice payment record after validating business rules.
        /// </summary>
        /// <param name="model">The received invoice payment data transfer object containing payment details.</param>
        /// <returns>An action result containing the created payment view model or error response.</returns>

        [HttpPost("invoice/payment/received")]
        public async Task<IActionResult> CreateReceivedInvoicePaymentAsync([FromBody] ReceivedInvoicePaymentDto model)
        {
            // Get the currently logged-in employee
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Log starting permission checks
            _logger.LogInfo("Starting permission checks for EmployeeId: {EmployeeId}", loggedInEmployee.Id);

            // Initiate permission check tasks asynchronously
            var adminPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.CollectionAdmin, loggedInEmployee.Id);
            });

            var addPaymentPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.AddPayment, loggedInEmployee.Id);
            });

            // Await all permission checks to complete concurrently
            await Task.WhenAll(adminPermissionTask, addPaymentPermissionTask);

            // Capture permission results
            var hasAdminPermission = adminPermissionTask.Result;
            var hasAddPaymentPermission = addPaymentPermissionTask.Result;

            // Log permission results for audit
            _logger.LogInfo("Permission results for EmployeeId {EmployeeId}: Admin={Admin}, Add Payment={AddPayment}",
                loggedInEmployee.Id, hasAdminPermission, hasAddPaymentPermission);

            // Check if user has any relevant permission; if none, deny access
            if (!hasAdminPermission && !hasAddPaymentPermission)
            {
                _logger.LogWarning("Permission denied for EmployeeId {EmployeeId} - No collection-related permissions found.", loggedInEmployee.Id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse(
                    "Access Denied",
                    "User does not have permission to access collection data.",
                    403));
            }

            // Optionally log success or continue with further processing here
            _logger.LogInfo("Permission granted for EmployeeId {EmployeeId} - Proceeding with collection access.", loggedInEmployee.Id);

            // Validate input model
            if (model == null)
            {
                _logger.LogWarning("Received invoice payment creation request with null model");
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid model", "Request payload cannot be null", 400));
            }

            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Retrieve invoice with tenant isolation and no tracking for read-only access
            var invoice = await _context.Invoices
                .AsNoTracking()
                .FirstOrDefaultAsync(i => i.Id == model.InvoiceId && i.TenantId == tenantId);

            if (invoice == null)
            {
                _logger.LogWarning("Invoice not found for ID {InvoiceId} and TenantId {TenantId}", model.InvoiceId, tenantId);
                return NotFound(ApiResponse<object>.ErrorResponse("Invoice not found", "The specified invoice does not exist", 404));
            }

            // Check if invoice is already marked as completed
            if (invoice.MarkAsCompleted)
            {
                _logger.LogWarning("Attempt to add payment to completed invoice {InvoiceId}", model.InvoiceId);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Cannot add received payment to completed invoice",
                    "Payments cannot be added to invoices that are already marked as completed", 400));
            }

            // Validate payment received date is not in the future
            if (model.PaymentReceivedDate.Date > DateTime.UtcNow.Date)
            {
                _logger.LogWarning("Future payment date {PaymentReceivedDate} provided for invoice {InvoiceId}",
                    model.PaymentReceivedDate, model.InvoiceId);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Payment received date cannot be in the future",
                    "The payment received date must not be later than the current date", 400));
            }

            // Validate client submitted date is not later than payment received date
            if (invoice.ClientSubmitedDate.Date > model.PaymentReceivedDate.Date)
            {
                _logger.LogWarning("Client submitted date {ClientSubmitedDate} is later than payment received date {PaymentReceivedDate} for invoice {InvoiceId}",
                    invoice.ClientSubmitedDate, model.PaymentReceivedDate, model.InvoiceId);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Client submission date cannot be later than the payment received date",
                    "The client submission date cannot be later than the payment received date", 400));
            }

            // Retrieve all previous payments for the given invoice and tenant in a single, efficient query.
            var receivedInvoicePayments = await _context.ReceivedInvoicePayments
                .Where(rip => rip.InvoiceId == invoice.Id && rip.TenantId == tenantId)
                .Select(rip => rip.Amount) // Only select required field for better performance.
                .ToListAsync();

            // Calculate the sum of all previous payments to determine the total paid so far.
            var previousPaidAmount = receivedInvoicePayments.Sum();
            var totalPaidAmount = previousPaidAmount + model.Amount;

            // Compute the invoice's total amount payable including taxes.
            var totalAmount = invoice.BasicAmount + invoice.TaxAmount;

            // Business rule validation: Prevent the overpayment scenario.
            if (totalPaidAmount > totalAmount)
            {
                // Log the details for easier debugging and audit trails.
                _logger.LogWarning(
                    "Overpayment attempt detected. InvoiceId: {InvoiceId}, TenantId: {TenantId}, TotalInvoiceAmount: {TotalInvoiceAmount}, PreviousPaidAmount: {PreviousPaidAmount}, AttemptedPayment: {AttemptedPayment}, CalculatedTotalPaid: {TotalPaidAmount}.",
                    invoice.Id, tenantId, totalAmount, previousPaidAmount, model.Amount, totalPaidAmount);

                // Return a bad request response with a clear, actionable error message.
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Invalid payment: total paid amount exceeds invoice total.",
                    $"The total of existing payments ({previousPaidAmount}) plus the new payment ({model.Amount}) would exceed the invoice total ({totalAmount}). Please verify payment details.",
                    400));
            }


            try
            {
                // Map DTO to entity and set creation metadata
                var receivedInvoicePayment = _mapper.Map<ReceivedInvoicePayment>(model);
                receivedInvoicePayment.CreatedAt = DateTime.UtcNow;
                receivedInvoicePayment.CreatedById = loggedInEmployee.Id;
                receivedInvoicePayment.TenantId = tenantId;

                // Add new payment record and save changes
                _context.ReceivedInvoicePayments.Add(receivedInvoicePayment);
                await _context.SaveChangesAsync();

                // Map entity to view model for response
                var response = _mapper.Map<ReceivedInvoicePaymentVM>(receivedInvoicePayment);

                _logger.LogInfo("Successfully created received payment {PaymentId} for invoice {InvoiceId}",
                    receivedInvoicePayment.Id, model.InvoiceId);

                return StatusCode(201, ApiResponse<object>.SuccessResponse(response, "Payment invoice received successfully", 201));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred while creating received payment for invoice {InvoiceId}", model.InvoiceId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse(
                    "Internal server error",
                    "An unexpected error occurred while processing the request", 500));
            }
        }

        /// <summary>
        /// Adds a comment to the specified invoice, validating model and invoice existence.
        /// </summary>
        /// <param name="model">DTO containing InvoiceId and Comment text.</param>
        /// <returns>201 Created with comment details, or error codes for validation/invoice not found.</returns>

        [HttpPost("invoice/add/comment")]
        public async Task<IActionResult> AddCommentToInvoiceAsync([FromBody] InvoiceCommentDto model)
        {
            // Get the currently logged-in employee
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Log starting permission checks
            _logger.LogInfo("Starting permission checks for EmployeeId: {EmployeeId}", loggedInEmployee.Id);

            // Initiate permission check tasks asynchronously
            var adminPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.CollectionAdmin, loggedInEmployee.Id);
            });

            var viewPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.ViewCollection, loggedInEmployee.Id);
            });

            var createPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.CreateCollection, loggedInEmployee.Id);
            });

            var editPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.EditCollection, loggedInEmployee.Id);
            });

            var addPaymentPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.AddPayment, loggedInEmployee.Id);
            });

            // Await all permission checks to complete concurrently
            await Task.WhenAll(adminPermissionTask, viewPermissionTask, createPermissionTask, editPermissionTask, addPaymentPermissionTask);

            // Capture permission results
            var hasAdminPermission = adminPermissionTask.Result;
            var hasViewPermission = viewPermissionTask.Result;
            var hasCreatePermission = createPermissionTask.Result;
            var hasEditPermission = editPermissionTask.Result;
            var hasAddPaymentPermission = addPaymentPermissionTask.Result;

            // Log permission results for audit
            _logger.LogInfo("Permission results for EmployeeId {EmployeeId}: Admin={Admin}, View={View}, Create={Create}, Edit={Edit}, Add Payment={AddPayment}",
                loggedInEmployee.Id, hasAdminPermission, hasViewPermission, hasCreatePermission, hasEditPermission, hasAddPaymentPermission);

            // Check if user has any relevant permission; if none, deny access
            if (!hasAdminPermission && !hasViewPermission && !hasCreatePermission && !hasEditPermission && !hasAddPaymentPermission)
            {
                _logger.LogWarning("Permission denied for EmployeeId {EmployeeId} - No collection-related permissions found.", loggedInEmployee.Id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse(
                    "Access Denied",
                    "User does not have permission to access collection data.",
                    403));
            }

            // Optionally log success or continue with further processing here
            _logger.LogInfo("Permission granted for EmployeeId {EmployeeId} - Proceeding with collection access.", loggedInEmployee.Id);

            // Validate incoming data early to avoid unnecessary database calls.
            if (string.IsNullOrWhiteSpace(model.Comment))
            {
                _logger.LogWarning("Invalid or missing comment data for InvoiceId {InvoiceId}, TenantId {TenantId}", model.InvoiceId, tenantId);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Invalid comment data",
                    "The comment text and model must not be null or empty.",
                    400));
            }

            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Find the target invoice for the specified tenant.
            var invoice = await _context.Invoices
                .FirstOrDefaultAsync(i => i.Id == model.InvoiceId && i.TenantId == tenantId);

            if (invoice == null)
            {
                _logger.LogWarning("Cannot add comment—invoice not found. InvoiceId {InvoiceId}, TenantId {TenantId}", model.InvoiceId, tenantId);
                return NotFound(ApiResponse<object>.ErrorResponse(
                    "Invoice not found",
                    $"Invoice with ID '{model.InvoiceId}' does not exist for the specified tenant.",
                    404));
            }

            // Construct the new comment entity with required audit metadata.
            var comment = new InvoiceComment
            {
                Id = Guid.NewGuid(),
                Comment = model.Comment.Trim(),
                InvoiceId = model.InvoiceId,
                CreatedAt = DateTime.UtcNow,
                CreatedById = loggedInEmployee.Id,
                TenantId = tenantId
            };

            _context.InvoiceComments.Add(comment);
            await _context.SaveChangesAsync();

            _logger.LogInfo("Added new comment to invoice {InvoiceId} by employee {EmployeeId}, TenantId {TenantId}",
                comment.InvoiceId, loggedInEmployee.Id, tenantId);

            var response = _mapper.Map<InvoiceCommentVM>(comment);

            // Return successful creation with comment details.
            return StatusCode(201, ApiResponse<object>.SuccessResponse(
                response,
                "Comment added to invoice successfully.",
                201));
        }

        #endregion

        #region =================================================================== Put Functions ===================================================================

        /// <summary>
        /// Updates an existing invoice if it exists, has no payments, and the model is valid.
        /// </summary>
        /// <param name="id">The unique identifier of the invoice to update.</param>
        /// <param name="model">The updated invoice data transfer object.</param>
        /// <returns>Success response on update, or appropriate error if validation fails.</returns>

        [HttpPut("invoice/edit/{id}")]
        public async Task<IActionResult> UpdateInvoiceAsync(Guid id, [FromBody] InvoiceDto model)
        {
            // Get the currently logged-in employee
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Log starting permission checks
            _logger.LogInfo("Starting permission checks for EmployeeId: {EmployeeId}", loggedInEmployee.Id);

            // Initiate permission check tasks asynchronously
            var adminPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.CollectionAdmin, loggedInEmployee.Id);
            });

            var editPermissionTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await _permission.HasPermission(PermissionsMaster.EditCollection, loggedInEmployee.Id);
            });

            // Await all permission checks to complete concurrently
            await Task.WhenAll(adminPermissionTask, editPermissionTask);

            // Capture permission results
            var hasAdminPermission = adminPermissionTask.Result;
            var hasEditPermission = editPermissionTask.Result;

            // Log permission results for audit
            _logger.LogInfo("Permission results for EmployeeId {EmployeeId}: Admin={Admin}, Process={Process}",
                loggedInEmployee.Id, hasAdminPermission, hasEditPermission);

            // Check if user has any relevant permission; if none, deny access
            if (!hasAdminPermission && !hasEditPermission)
            {
                _logger.LogWarning("Permission denied for EmployeeId {EmployeeId} - No collection-related permissions found.", loggedInEmployee.Id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse(
                    "Access Denied",
                    "User does not have permission to access collection data.",
                    403));
            }

            // Optionally log success or continue with further processing here
            _logger.LogInfo("Permission granted for EmployeeId {EmployeeId} - Proceeding with collection access.", loggedInEmployee.Id);

            // Validate route and model ID consistency
            if (!model.Id.HasValue || id != model.Id)
            {
                _logger.LogWarning("Invoice ID mismatch: route ID {RouteId} does not match model ID {ModelId}", id, model.Id ?? Guid.Empty);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Invalid invoice ID",
                    "The invoice ID in the URL does not match the ID in the request body.",
                    400));
            }

            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();

            // Retrieve the invoice with tenant isolation
            var invoice = await _context.Invoices
                .FirstOrDefaultAsync(i => i.Id == id && i.TenantId == tenantId);

            if (invoice == null)
            {
                _logger.LogWarning("Invoice not found for ID {InvoiceId} and TenantId {TenantId}", id, tenantId);
                return NotFound(ApiResponse<object>.ErrorResponse(
                    "Invoice not found",
                    "The specified invoice does not exist for this tenant.",
                    404));
            }

            // Fetch project
            var project = await _context.Projects
                .FirstOrDefaultAsync(p => p.Id == model.ProjectId && p.TenantId == tenantId);
            if (project == null)
            {
                _logger.LogWarning("Project not found: ProjectId {ProjectId}, TenantId {TenantId}",
                    model.ProjectId, tenantId);
                return NotFound(ApiResponse<object>.ErrorResponse("Project not found", "Project not found", 404));
            }

            // Prevent modification if any payment has already been received
            var receivedPaymentExists = await _context.ReceivedInvoicePayments
                .AnyAsync(rip => rip.InvoiceId == id && rip.TenantId == tenantId);

            if (receivedPaymentExists)
            {
                _logger.LogWarning("Update blocked: Payment already received for InvoiceId {InvoiceId}, TenantId {TenantId}", id, tenantId);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Invoice cannot be updated",
                    "This invoice has received payments and cannot be modified.",
                    400));
            }

            try
            {
                var invoiceStateBeforeChange = _updateLogHelper.EntityToBsonDocument(invoice);

                // Map updated data to existing invoice entity
                _mapper.Map(model, invoice);
                invoice.UpdatedAt = DateTime.UtcNow;
                invoice.UpdatedById = loggedInEmployee.Id;

                // Handle attachment updates if provided
                if (model.Attachments?.Any() ?? false)
                {
                    var inactiveDocumentIds = model.Attachments
                        .Where(a => !a.IsActive && a.DocumentId.HasValue)
                        .Select(a => a.DocumentId!.Value)
                        .ToList();

                    var newAttachments = model.Attachments
                        .Where(a => a.IsActive && !string.IsNullOrWhiteSpace(a.Base64Data))
                        .ToList();

                    // Remove inactive attachments
                    if (inactiveDocumentIds.Any())
                    {
                        var existingInvoiceAttachments = await _context.InvoiceAttachments
                            .AsNoTracking()
                            .Where(ia => inactiveDocumentIds.Contains(ia.DocumentId) && ia.TenantId == tenantId)
                            .ToListAsync();

                        _context.InvoiceAttachments.RemoveRange(existingInvoiceAttachments);
                        _logger.LogInfo("Removed {Count} inactive attachments for InvoiceId {InvoiceId}", existingInvoiceAttachments.Count, id);
                    }

                    // Process and upload new attachments
                    if (newAttachments.Any())
                    {
                        var batchId = Guid.NewGuid();
                        var documents = new List<Document>();
                        var invoiceAttachments = new List<InvoiceAttachment>();

                        foreach (var attachment in newAttachments)
                        {
                            string base64Data = attachment.Base64Data?.Split(',').LastOrDefault() ?? string.Empty;

                            if (string.IsNullOrWhiteSpace(base64Data))
                            {
                                _logger.LogWarning("Base64 data missing for attachment: {FileName}", attachment.FileName ?? "Unknown");
                                return BadRequest(ApiResponse<object>.ErrorResponse(
                                    "Invalid attachment data",
                                    "Base64 data is missing or malformed for one or more attachments.",
                                    400));
                            }

                            var contentType = _s3Service.GetContentTypeFromBase64(base64Data);
                            var fileName = _s3Service.GenerateFileName(contentType, tenantId, "invoice");
                            var objectKey = $"tenant-{tenantId}/Project/{model.ProjectId}/Invoice/{fileName}";

                            // Upload file to S3
                            await _s3Service.UploadFileAsync(base64Data, contentType, objectKey);

                            // Create document record
                            var document = new Document
                            {
                                Id = Guid.NewGuid(),
                                BatchId = batchId,
                                UploadedById = loggedInEmployee.Id,
                                FileName = attachment.FileName ?? fileName,
                                ContentType = contentType,
                                S3Key = objectKey,
                                FileSize = attachment.FileSize,
                                UploadedAt = DateTime.UtcNow,
                                TenantId = tenantId
                            };
                            documents.Add(document);

                            // Link document to invoice
                            var invoiceAttachment = new InvoiceAttachment
                            {
                                InvoiceId = invoice.Id,
                                DocumentId = document.Id,
                                TenantId = tenantId
                            };
                            invoiceAttachments.Add(invoiceAttachment);
                        }

                        _context.Documents.AddRange(documents);
                        _context.InvoiceAttachments.AddRange(invoiceAttachments);
                        _logger.LogInfo("Added {Count} new attachments to InvoiceId {InvoiceId}", invoiceAttachments.Count, id);
                    }
                }

                // Save all changes in a single transaction
                await _context.SaveChangesAsync();

                await _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
                {
                    EntityId = invoice.Id.ToString(),
                    UpdatedById = loggedInEmployee.Id.ToString(),
                    OldObject = invoiceStateBeforeChange,
                    UpdatedAt = DateTime.UtcNow
                }, "InvoiceModificationLog");

                _logger.LogInfo("Invoice {InvoiceId} updated successfully by EmployeeId {EmployeeId}, TenantId {TenantId}",
                    invoice.Id, loggedInEmployee.Id, tenantId);

                // Build response
                var response = _mapper.Map<InvoiceListVM>(invoice);
                response.Project = _mapper.Map<BasicProjectVM>(project);
                response.UpdatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
                response.BalanceAmount = response.BasicAmount + response.TaxAmount;

                return Ok(ApiResponse<object>.SuccessResponse(response, "Invoice updated successfully", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred while updating InvoiceId {InvoiceId}, TenantId {TenantId}", id, tenantId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse(
                    "Internal server error",
                    "An unexpected error occurred while updating the invoice.",
                    500));
            }
        }

        /// <summary>
        /// Marks the specified invoice as completed if it exists and is not already completed.
        /// </summary>
        /// <param name="invoiceId">The unique identifier of the invoice to mark as completed.</param>
        /// <returns>An action result indicating success or the nature of the error.</returns>

        [HttpPut("invoice/marked/completed/{invoiceId}")]
        public async Task<IActionResult> MarkAsCompletedAsync(Guid invoiceId)
        {
            // Create a scope for permission service resolution
            using var scope = _serviceScopeFactory.CreateScope();
            var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
            var _updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();

            // Get the currently logged-in employee
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Log starting permission checks
            _logger.LogInfo("Starting permission checks for EmployeeId: {EmployeeId}", loggedInEmployee.Id);

            // Capture permission results
            var hasAdminPermission = await _permission.HasPermission(PermissionsMaster.CollectionAdmin, loggedInEmployee.Id);

            // Log permission results for audit
            _logger.LogInfo("Permission results for EmployeeId {EmployeeId}: Admin={Admin}",
                loggedInEmployee.Id, hasAdminPermission);

            // Check if user has any relevant permission; if none, deny access
            if (!hasAdminPermission)
            {
                _logger.LogWarning("Permission denied for EmployeeId {EmployeeId} - No collection-related permissions found.", loggedInEmployee.Id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse(
                    "Access Denied",
                    "User does not have permission to access collection data.",
                    403));
            }

            // Optionally log success or continue with further processing here
            _logger.LogInfo("Permission granted for EmployeeId {EmployeeId} - Proceeding with collection access.", loggedInEmployee.Id);

            // Create a new async database context for the current request's scope.
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Attempt to find the invoice with tenant isolation; use AsNoTracking if no updates needed (but here we update so tracking is okay).
            var invoice = await _context.Invoices
                .FirstOrDefaultAsync(i => i.Id == invoiceId && i.TenantId == tenantId);

            // Log and return 404 if the invoice does not exist.
            if (invoice == null)
            {
                _logger.LogWarning("Invoice not found for ID {InvoiceId} and TenantId {TenantId}", invoiceId, tenantId);
                return NotFound(ApiResponse<object>.ErrorResponse("Invoice not found", "The specified invoice does not exist", 404));
            }

            // If the invoice is already marked as completed, log and return meaningful error.
            if (invoice.MarkAsCompleted)
            {
                _logger.LogWarning("Attempt to mark already completed invoice {InvoiceId} as completed by user {UserId}", invoiceId, loggedInEmployee.Id);
                return BadRequest(ApiResponse<object>.ErrorResponse(
                    "Invoice already completed",
                    "Invoice is already marked as completed", 400));
            }

            try
            {
                var invoiceStateBeforeChange = _updateLogHelper.EntityToBsonDocument(invoice);

                // Mark invoice as completed.
                invoice.MarkAsCompleted = true;
                invoice.UpdatedAt = DateTime.UtcNow;
                invoice.UpdatedById = loggedInEmployee.Id;

                // Persist the change to the database.
                await _context.SaveChangesAsync();

                _logger.LogInfo("Invoice {InvoiceId} marked as completed by user {UserId}", invoiceId, loggedInEmployee.Id);

                await _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
                {
                    EntityId = invoice.Id.ToString(),
                    UpdatedById = loggedInEmployee.Id.ToString(),
                    OldObject = invoiceStateBeforeChange,
                    UpdatedAt = DateTime.UtcNow
                }, "InvoiceModificationLog");

                var response = _mapper.Map<InvoiceListVM>(invoice);
                response.UpdatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
                response.BalanceAmount = response.BasicAmount + response.TaxAmount;

                return Ok(ApiResponse<object>.SuccessResponse(response, "Invoice is marked as completed successfully", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred while marking invoice {InvoiceId} as completed by user {UserId}", invoiceId, loggedInEmployee.Id);
                return StatusCode(500, ApiResponse<object>.ErrorResponse(
                    "Internal server error",
                    "An unexpected error occurred while processing the request", 500));
            }
        }

        #endregion

        #region =================================================================== Helper Functions ===================================================================

        /// <summary>
        /// Loads invoice comments asynchronously with related metadata.
        /// </summary>
        private async Task<List<InvoiceComment>> LoadInvoiceCommentsAsync(Guid invoiceId, Guid tenantId)
        {
            await using var context = await _dbContextFactory.CreateDbContextAsync();
            return await context.InvoiceComments
                .Include(ic => ic.CreatedBy).ThenInclude(e => e!.JobRole)
                .AsNoTracking()
                .Where(ic => ic.InvoiceId == invoiceId && ic.TenantId == tenantId)
                .ToListAsync();
        }

        /// <summary>
        /// Loads invoice attachments and their upload metadata asynchronously.
        /// </summary>
        private async Task<List<InvoiceAttachment>> LoadInvoiceAttachmentsAsync(Guid invoiceId, Guid tenantId)
        {
            await using var context = await _dbContextFactory.CreateDbContextAsync();
            return await context.InvoiceAttachments
                .Include(ia => ia.Document)
                    .ThenInclude(d => d!.UploadedBy)
                        .ThenInclude(e => e!.JobRole)
                .AsNoTracking()
                .Where(ia => ia.InvoiceId == invoiceId && ia.TenantId == tenantId && ia.Document != null && ia.Document.UploadedBy != null)
                .ToListAsync();
        }

        /// <summary>
        /// Loads received invoice payment records asynchronously with creator metadata.
        /// </summary>
        private async Task<List<ReceivedInvoicePayment>> LoadReceivedInvoicePaymentsAsync(Guid invoiceId, Guid tenantId)
        {
            await using var context = await _dbContextFactory.CreateDbContextAsync();
            return await context.ReceivedInvoicePayments
                .Include(rip => rip.PaymentAdjustmentHead)
                .Include(rip => rip.CreatedBy).ThenInclude(e => e!.JobRole)
                .AsNoTracking()
                .Where(rip => rip.InvoiceId == invoiceId && rip.TenantId == tenantId)
                .ToListAsync();
        }

        #endregion

    }
}
