using AutoMapper;
using FirebaseAdmin.Messaging;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Helpers.Utility;
using Marco.Pms.Model.DocumentManager;
using Marco.Pms.Model.Dtos.DocumentManager;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Filters;
using Marco.Pms.Model.MongoDBModels.Utility;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.DocumentManager;
using Marco.Pms.Services.Service;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
using System.Text.RegularExpressions;
using Document = Marco.Pms.Model.DocumentManager.Document;

namespace Marco.Pms.Services.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class DocumentController : ControllerBase
    {
        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
        private readonly IServiceScopeFactory _serviceScope;
        private readonly UserHelper _userHelper;
        private readonly ILoggingService _logger;
        private readonly IMapper _mapper;
        private readonly Guid tenantId;

        private static readonly Guid ProjectEntity = Guid.Parse("c8fe7115-aa27-43bc-99f4-7b05fabe436e");
        private static readonly Guid EmployeeEntity = Guid.Parse("dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7");
        private static readonly string Collection = "DocumentModificationLog";

        public DocumentController(IDbContextFactory<ApplicationDbContext> dbContextFactory,
            IServiceScopeFactory serviceScope,
            UserHelper userHelper,
            ILoggingService logger,
            IMapper mapper)
        {
            _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
            _serviceScope = serviceScope ?? throw new ArgumentNullException(nameof(serviceScope));
            _userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
            tenantId = userHelper.GetTenantId();
        }

        [HttpGet("list/{entityTypeId}/entity/{entityId}")]
        public async Task<IActionResult> GetDocumentListAsync(Guid entityTypeId, Guid entityId, [FromQuery] string? filter, [FromQuery] string? searchString,
            [FromQuery] bool isActive = true, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
        {
            using var scope = _serviceScope.CreateScope();
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();

            try
            {
                _logger.LogInfo("Fetching documents for EntityTypeId: {EntityTypeId}, EntityId: {EntityId}", entityTypeId, entityId);

                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

                // Check global permission
                var hasViewPermission = false;
                if (ProjectEntity == entityTypeId)
                {
                    hasViewPermission = await _permission.HasPermission(PermissionsMaster.ViewDocument, loggedInEmployee.Id, entityId);
                }
                else if (EmployeeEntity == entityTypeId)
                {
                    hasViewPermission = await _permission.HasPermission(PermissionsMaster.ViewDocument, loggedInEmployee.Id);
                }

                if (!hasViewPermission && loggedInEmployee.Id != entityId)
                {
                    _logger.LogWarning("Access Denied for Employee {EmployeeId} on EntityId {EntityId}", loggedInEmployee.Id, entityId);
                    return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to view documents", 403));
                }

                // Validate entity type
                if (ProjectEntity != entityTypeId && EmployeeEntity != entityTypeId)
                {
                    _logger.LogWarning("Invalid EntityTypeId: {EntityTypeId}", entityTypeId);
                    return NotFound(ApiResponse<object>.ErrorResponse("Entity type not found", "Entity Type not found in database", 404));
                }

                // Project permission check
                if (ProjectEntity == entityTypeId)
                {
                    var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, entityId);
                    if (!hasProjectPermission)
                    {
                        _logger.LogWarning("Employee {EmployeeId} does not have project access for ProjectId {ProjectId}", loggedInEmployee.Id, entityId);
                        return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to access project documents", 403));
                    }
                }
                // Employee validation
                else if (EmployeeEntity == entityTypeId)
                {
                    var isEmployeeExists = await _context.Employees
                        .AnyAsync(e => e.Id == entityId && e.TenantId == tenantId);

                    if (!isEmployeeExists)
                    {
                        _logger.LogWarning("Employee {EmployeeId} not found for Tenant {TenantId}", entityId, tenantId);
                        return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee not found in database", 404));
                    }
                }

                // Base Query (with includes to avoid lazy loading)
                IQueryable<DocumentAttachment> documentQuery = _context.DocumentAttachments
                    .AsNoTracking() // Optimization: Read-only query
                    .Include(da => da.UploadedBy)
                        .ThenInclude(e => e!.JobRole)
                    .Include(da => da.DocumentType!)
                        .ThenInclude(dt => dt.DocumentCategory)
                    .Where(da => da.EntityId == entityId &&
                                 da.DocumentType != null &&
                                 da.DocumentType.DocumentCategory != null &&
                                 da.DocumentType.DocumentCategory.EntityTypeId == entityTypeId &&
                                 da.IsActive == isActive &&
                                 da.TenantId == tenantId);

                // Apply filter if provided
                var documentFilter = TryDeserializeFilter(filter);
                if (documentFilter != null)
                {
                    _logger.LogInfo("Applying document filters for EntityId {EntityId}", entityId);

                    if (documentFilter.IsVerified != null)
                        documentQuery = documentQuery.Where(da => da.IsVerified == documentFilter.IsVerified);

                    if (documentFilter.DocumentCategoryIds?.Any() ?? false)
                        documentQuery = documentQuery.Where(da => documentFilter.DocumentCategoryIds.Contains(da.DocumentType!.DocumentCategoryId));

                    if (documentFilter.DocumentTypeIds?.Any() ?? false)
                        documentQuery = documentQuery.Where(da => documentFilter.DocumentTypeIds.Contains(da.DocumentTypeId));

                    if (documentFilter.DocumentTagIds?.Any() ?? false)
                    {
                        var filteredIds = await _context.AttachmentTagMappings
                            .AsNoTracking()
                            .Where(at => at.DocumentTag != null && documentFilter.DocumentTagIds.Contains(at.DocumentTag.Id) && at.TenantId == tenantId)
                            .Select(at => at.AttachmentId)
                            .ToListAsync();

                        documentQuery = documentQuery.Where(da => filteredIds.Contains(da.Id));
                    }

                    if (documentFilter.UploadedByIds?.Any() ?? false)
                        documentQuery = documentQuery.Where(da => documentFilter.UploadedByIds.Contains(da.UploadedById));

                    // Date Range Filtering (Uploaded vs Verified date)
                    if (documentFilter.StartDate != null && documentFilter.EndDate != null)
                    {
                        if (documentFilter.IsUploadedAt)
                        {
                            documentQuery = documentQuery.Where(da =>
                                da.UploadedAt.Date >= documentFilter.StartDate.Value.Date &&
                                da.UploadedAt.Date <= documentFilter.EndDate.Value.Date);
                        }
                        else
                        {
                            documentQuery = documentQuery.Where(da =>
                                da.VerifiedAt.HasValue &&
                                da.VerifiedAt.Value.Date >= documentFilter.StartDate.Value.Date &&
                                da.VerifiedAt.Value.Date <= documentFilter.EndDate.Value.Date);
                        }
                    }
                    else
                    {
                        documentQuery = documentQuery.Where(da => da.IsCurrentVersion);
                    }
                }
                else
                {
                    // Default: only latest version
                    documentQuery = documentQuery.Where(da => da.IsCurrentVersion);
                }

                // Apply search filter
                if (!string.IsNullOrWhiteSpace(searchString))
                {
                    documentQuery = documentQuery.Where(da =>
                        da.Name.Contains(searchString) ||
                        (da.DocumentId != null && da.DocumentId.Contains(searchString))
                    );
                }

                var totalCount = await documentQuery.CountAsync();
                var totalPages = (int)Math.Ceiling((double)totalCount / pageSize); ;

                // Apply pagination & ordering
                var documents = await documentQuery
                    .OrderByDescending(t => t.UploadedAt)
                    .Skip((pageNumber - 1) * pageSize)
                    .Take(pageSize)
                    .ToListAsync();

                var documentIds = documents.Select(da => da.Id).ToList();

                // Get versions for the selected documents
                var versions = await _context.AttachmentVersionMappings
                    .AsNoTracking()
                    .Where(av => documentIds.Contains(av.ChildAttachmentId) && av.TenantId == tenantId)
                    .ToListAsync();

                // Map to ViewModel
                var documentListVMs = documents.Select(doc =>
                {
                    var version = versions.FirstOrDefault(v => v.ChildAttachmentId == doc.Id);
                    var vm = _mapper.Map<DocumentListVM>(doc);
                    vm.ParentAttachmentId = version?.ParentAttachmentId;
                    vm.Version = version?.Version ?? 1;
                    return vm;
                }).ToList();

                _logger.LogInfo("Fetched {Count} documents for EntityId {EntityId}", documentListVMs.Count, entityId);

                var response = new
                {
                    CurrentFilter = documentFilter,
                    CurrentPage = pageNumber,
                    TotalPages = totalPages,
                    TotalEntites = totalCount,
                    Data = documentListVMs,
                };

                return Ok(ApiResponse<object>.SuccessResponse(response, "Document list fetched successfully", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error fetching documents for EntityId {EntityId}", entityId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error", ex.Message, 500));
            }
        }

        [HttpGet("get/details/{id}")]
        public async Task<IActionResult> GetDetailsAsync(Guid id)
        {
            _logger.LogInfo("Starting GetDetails API for AttachmentId: {AttachmentId}", id);

            // Create a new DbContext instance to fetch data
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Create a new scoped service provider to resolve scoped dependencies
            using var scope = _serviceScope.CreateScope();

            // Resolve the permission service from the scoped service provider
            var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();

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

            _logger.LogDebug("Logged in employee id: {EmployeeId}", loggedInEmployee.Id);

            // Fetch the AttachmentVersionMapping with all necessary related data loaded eagerly
            var versionMapping = await _context.AttachmentVersionMappings
                .Include(av => av.ChildAttachment)
                    .ThenInclude(da => da!.UploadedBy)
                        .ThenInclude(e => e!.JobRole)
                .Include(av => av.ChildAttachment)
                    .ThenInclude(da => da!.UpdatedBy)
                        .ThenInclude(e => e!.JobRole)
                .Include(av => av.ChildAttachment)
                    .ThenInclude(da => da!.VerifiedBy)
                        .ThenInclude(e => e!.JobRole)
                .Include(av => av.ChildAttachment)
                    .ThenInclude(da => da!.DocumentType)
                        .ThenInclude(dt => dt!.DocumentCategory)
                .Include(av => av.ChildAttachment)
                    .ThenInclude(da => da!.Document)
                .FirstOrDefaultAsync(av => av.ChildAttachmentId == id && av.TenantId == tenantId);

            // If no mapping found, return 404
            if (versionMapping == null || versionMapping.ChildAttachment == null)
            {
                _logger.LogWarning("AttachmentVersionMapping not found for AttachmentId: {AttachmentId}, TenantId: {TenantId}",
                    id, tenantId);
                return NotFound(ApiResponse<object>.ErrorResponse("Document Attachment not found", "Document Attachment not found in database", 404));
            }

            // Check if the logged in employee has permission to view the document OR is the owner of the attachment entity
            var hasViewPermission = false;
            if (ProjectEntity == versionMapping.ChildAttachment.DocumentType!.DocumentCategory!.EntityTypeId)
            {
                hasViewPermission = await _permission.HasPermission(PermissionsMaster.ViewDocument, loggedInEmployee.Id, versionMapping.ChildAttachment.EntityId);
            }
            else if (EmployeeEntity == versionMapping.ChildAttachment.DocumentType!.DocumentCategory!.EntityTypeId)
            {
                hasViewPermission = await _permission.HasPermission(PermissionsMaster.ViewDocument, loggedInEmployee.Id);
            }

            if (!hasViewPermission && loggedInEmployee.Id != versionMapping.ChildAttachment.EntityId)
            {
                _logger.LogWarning("Access Denied for Employee {EmployeeId} on EntityId {EntityId}",
                    loggedInEmployee.Id, versionMapping.ChildAttachment.EntityId);
                return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to view documents", 403));
            }
            var tags = await _context.AttachmentTagMappings
                .Include(at => at.DocumentTag)
                .Where(at => at.AttachmentId == versionMapping.ChildAttachmentId && at.TenantId == tenantId && at.DocumentTag != null)
                .Select(at => new DocumentTagDto
                {
                    Name = at.DocumentTag!.Name,
                    IsActive = true
                })
                .ToListAsync();
            // Map the domain entity to the view model
            var documentAttachmentVM = _mapper.Map<DocumentAttachmentDetailsVM>(versionMapping.ChildAttachment);
            documentAttachmentVM.Version = versionMapping.Version;
            documentAttachmentVM.ParentAttachmentId = versionMapping.ParentAttachmentId;
            documentAttachmentVM.Tags = tags;
            documentAttachmentVM.FileSize = versionMapping.ChildAttachment.Document!.FileSize;
            documentAttachmentVM.ContentType = versionMapping.ChildAttachment.Document.ContentType;

            _logger.LogInfo("Document details fetched successfully for AttachmentId: {AttachmentId}", id);

            // Return success response with document details
            return Ok(ApiResponse<object>.SuccessResponse(documentAttachmentVM, "Document details fetched successfully", 200));
        }

        [HttpGet("get/filter/{entityTypeId}")]
        public async Task<IActionResult> GetFilterObjectAsync(Guid entityTypeId)
        {
            // Log: Starting filter fetch process
            _logger.LogInfo("Initiating GetFilterObjectAsync to retrieve document filter data.");

            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScope.CreateScope();

            // Get current logged-in employee
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
            _logger.LogDebug("Fetched current employee: {EmployeeId}", loggedInEmployee.Id);

            // Fetch all relevant document attachments for the tenant with related data
            var documentList = await _context.DocumentAttachments
                .Include(da => da.UploadedBy)
                .Include(da => da.DocumentType)
                    .ThenInclude(dt => dt!.DocumentCategory)
                .Where(da => da.DocumentType != null &&
                    da.DocumentType.DocumentCategory != null &&
                    da.DocumentType.DocumentCategory.EntityTypeId == entityTypeId &&
                    da.TenantId == tenantId)
                .ToListAsync();
            _logger.LogInfo("Fetched {Count} document attachments for tenant {TenantId}", documentList.Count, tenantId);

            // Select IDs for attachments present in documentList
            var documentIds = documentList.Select(da => da.Id).ToList();

            // Preload tags for given ids
            var documentTags = await _context.AttachmentTagMappings
                .Where(at => documentIds.Contains(at.AttachmentId) && at.DocumentTag != null)
                .Select(at => new
                {
                    Id = at.DocumentTag!.Id,
                    Name = at.DocumentTag.Name
                })
                .Distinct()
                .ToListAsync();
            _logger.LogInfo("Loaded {Count} document tags", documentTags.Count);

            // Gather unique UploadedBy users
            var uploadedBy = documentList
                .Where(da => da.UploadedBy != null)
                .Select(da => new
                {
                    Id = da.UploadedBy!.Id,
                    Name = $"{da.UploadedBy.FirstName} {da.UploadedBy.LastName}"
                })
                .Distinct()
                .ToList();
            _logger.LogInfo("Collected {Count} unique uploaders", uploadedBy.Count);

            // Gather unique DocumentCategories
            var documentCategories = documentList
                .Where(da => da.DocumentType?.DocumentCategory != null)
                .Select(da => new
                {
                    Id = da.DocumentType!.DocumentCategory!.Id,
                    Name = da.DocumentType.DocumentCategory.Name
                })
                .Distinct()
                .ToList();
            _logger.LogInfo("Collected {Count} unique document categories", documentCategories.Count);

            // Gather unique DocumentTypes
            var documentTypes = documentList
                .Where(da => da.DocumentType != null)
                .Select(da => new
                {
                    Id = da.DocumentType!.Id,
                    Name = da.DocumentType.Name
                })
                .Distinct()
                .ToList();
            _logger.LogInfo("Collected {Count} unique document types", documentTypes.Count);

            // Compose response
            var response = new
            {
                UploadedBy = uploadedBy,
                DocumentCategory = documentCategories,
                DocumentType = documentTypes,
                DocumentTag = documentTags
            };

            _logger.LogInfo("Returning filter response successfully.");
            return Ok(ApiResponse<object>.SuccessResponse(response, "Filters for documents fetched successfully", 200));
        }

        [HttpGet("list/versions/{parentAttachmentId}")]
        public async Task<IActionResult> GetListAllVersionsAsync(Guid parentAttachmentId, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
        {
            _logger.LogInfo("Start fetching document versions for ParentAttachmentId: {ParentAttachmentId}", parentAttachmentId);

            // Create a new DbContext instance asynchronously
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScope.CreateScope();

            var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();

            try
            {
                // Retrieve currently logged in employee details for potential security or filtering checks
                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
                if (loggedInEmployee == null)
                {
                    _logger.LogWarning("No logged in employee found while fetching versions for ParentAttachmentId: {ParentAttachmentId}", parentAttachmentId);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", 401));
                }

                // Retrieve all version mappings linked to the parent attachment and tenant
                var versionMappingsQuery = _context.AttachmentVersionMappings
                    .Include(av => av.ChildAttachment)
                        .ThenInclude(da => da!.DocumentType)
                            .ThenInclude(dt => dt!.DocumentCategory)
                    .Include(av => av.ChildAttachment)
                        .ThenInclude(da => da!.UploadedBy)
                            .ThenInclude(e => e!.JobRole)
                    .Include(av => av.ChildAttachment)
                        .ThenInclude(da => da!.UpdatedBy)
                            .ThenInclude(e => e!.JobRole)
                    .Include(av => av.ChildAttachment)
                        .ThenInclude(da => da!.VerifiedBy)
                            .ThenInclude(e => e!.JobRole)
                    .Include(av => av.ChildAttachment)
                        .ThenInclude(da => da!.Document)
                    .Where(av => av.ParentAttachmentId == parentAttachmentId && av.TenantId == tenantId);

                var totalCount = await versionMappingsQuery.CountAsync();
                var totalPages = (int)Math.Ceiling((double)totalCount / pageSize); ;

                var versionMappings = await versionMappingsQuery
                    .OrderByDescending(da => da.ChildAttachment!.UploadedAt)
                    .Skip((pageNumber - 1) * pageSize)
                    .Take(pageSize)
                    .ToListAsync();

                var entityId = versionMappings.Select(av => av.ChildAttachment?.EntityId).FirstOrDefault();
                var entityTypeId = versionMappings.Select(av => av.ChildAttachment?.DocumentType?.DocumentCategory?.EntityTypeId).FirstOrDefault();

                // Check global permission
                var hasViewPermission = false;
                if (ProjectEntity == entityTypeId)
                {
                    hasViewPermission = await _permission.HasPermission(PermissionsMaster.ViewDocument, loggedInEmployee.Id, entityId);
                }
                else if (EmployeeEntity == entityTypeId)
                {
                    hasViewPermission = await _permission.HasPermission(PermissionsMaster.ViewDocument, loggedInEmployee.Id);
                }
                if (!hasViewPermission && loggedInEmployee.Id != entityId)
                {
                    _logger.LogWarning("Access Denied for Employee {EmployeeId} on EntityId {EntityId}", loggedInEmployee.Id, entityId ?? Guid.Empty);
                    return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to view documents", 403));
                }

                _logger.LogInfo("Found {Count} versions for ParentAttachmentId: {ParentAttachmentId}", versionMappings.Count, parentAttachmentId);

                // Map the retrieved child attachments to view models with version info
                var attachmentVersionVMs = versionMappings.Select(versionMapping =>
                {
                    var documentVM = _mapper.Map<AttachmentVersionVM>(versionMapping.ChildAttachment);
                    documentVM.Version = versionMapping.Version;
                    documentVM.FileSize = versionMapping.ChildAttachment!.Document!.FileSize;
                    documentVM.ContentType = versionMapping.ChildAttachment.Document!.ContentType;
                    return documentVM;
                }).ToList();

                var response = new
                {
                    CurrentPage = pageNumber,
                    TotalPages = totalPages,
                    TotalEntites = totalCount,
                    Data = attachmentVersionVMs,
                };

                _logger.LogInfo("Successfully mapped version data for ParentAttachmentId: {ParentAttachmentId}", parentAttachmentId);

                return Ok(ApiResponse<object>.SuccessResponse(response, "Document versions fetched successfully", 200));
            }
            catch (Exception ex)
            {
                // Log the exception and return an internal server error response
                _logger.LogError(ex, "Error occurred while fetching document versions for ParentAttachmentId: {ParentAttachmentId}", parentAttachmentId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("An error occurred while fetching document versions", 500));
            }
            finally
            {
                _logger.LogInfo("End processing GetAllVersions for ParentAttachmentId: {ParentAttachmentId}", parentAttachmentId);
            }
        }

        [HttpGet("get/version/{id}")]
        public async Task<IActionResult> GetAllVersionsAsync(Guid id)
        {
            // API endpoint to get all versions of an attachment by its ID
            _logger.LogInfo($"Fetching versions for attachment with ID: {id}");
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            // Create a new DbContext from the factory asynchronously
            using var scope = _serviceScope.CreateScope();
            // Create a new service scope for scoped services

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

            var versionMapping = await _context.AttachmentVersionMappings
                // Retrieve version mapping including the child attachment and its document
                .Include(av => av.ChildAttachment)
                    .ThenInclude(da => da!.Document)
                .Include(av => av.ChildAttachment)
                        .ThenInclude(da => da!.DocumentType)
                            .ThenInclude(dt => dt!.DocumentCategory)
                .FirstOrDefaultAsync(av => av.ChildAttachmentId == id);

            if (versionMapping == null || versionMapping.ChildAttachment == null || versionMapping.ChildAttachment.Document == null)
            // Return 404 if no version mapping or related entities found
            {
                _logger.LogWarning($"Version mapping not found for attachment ID: {id}");
                return NotFound(ApiResponse<object>.ErrorResponse("Version not found", "Version not found in database", 404));
            }
            var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
            var hasDownloadPermission = false;
            if (ProjectEntity == versionMapping.ChildAttachment.DocumentType?.DocumentCategory?.EntityTypeId)
            {
                hasDownloadPermission = await _permission.HasPermission(PermissionsMaster.DownloadDocument, loggedInEmployee.Id, versionMapping.ChildAttachment.EntityId);
            }
            else if (EmployeeEntity == versionMapping.ChildAttachment.DocumentType?.DocumentCategory?.EntityTypeId)
            {
                hasDownloadPermission = await _permission.HasPermission(PermissionsMaster.DownloadDocument, loggedInEmployee.Id);
            }

            if (!hasDownloadPermission && loggedInEmployee.Id != versionMapping.ChildAttachment.EntityId)
            {
                _logger.LogWarning("Access Denied for Employee {EmployeeId} on EntityId {EntityId} for downloading",
                    loggedInEmployee.Id, versionMapping.ChildAttachment.EntityId);
                return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to download documents", 403));
            }
            var s3Service = scope.ServiceProvider.GetRequiredService<S3UploadService>();
            // Resolve S3UploadService from the scope to generate pre-signed URL
            var preSignedUrl = s3Service.GeneratePreSignedUrl(versionMapping.ChildAttachment.Document.S3Key);
            // Generate pre-signed URL for the document stored in S3
            _logger.LogInfo($"Generated pre-signed URL for attachment ID: {id}");

            return Ok(ApiResponse<object>.SuccessResponse(preSignedUrl, "Pre-Signed Url for old version fetched successfully", 200));
            // Return the pre-signed URL with a success response
        }

        [HttpGet("get/tags")]
        public async Task<IActionResult> GetAllDocumentTagsAsync()
        {
            // Log: API endpoint execution started
            _logger.LogInfo("Executing GetAllDocumentTagsAsync to retrieve document tags for tenant.");

            // Immediately create DbContext asynchronously using the factory pattern for efficiency and DI compliance
            await using var context = await _dbContextFactory.CreateDbContextAsync();

            // Create a new DI scope for scoped services (e.g., user/context-specific dependencies)
            using var scope = _serviceScope.CreateScope();

            try
            {
                // Fetch the currently logged-in employee (auth/user context)
                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
                if (loggedInEmployee == null)
                {
                    _logger.LogWarning("Current employee could not be identified.");
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized", 401));
                }

                // Retrieve the tags that belong to the specified tenant and project only the names (performance: projection)
                var tags = await context.DocumentTagMasters
                    .Where(dt => dt.TenantId == tenantId)
                    .Select(dt => new
                    {
                        Name = dt.Name,
                        IsActive = true
                    })
                    .ToListAsync();

                _logger.LogInfo("Successfully retrieved {TagCount} document tags for tenant {TenantId}.", tags.Count, tenantId);

                // Return tags wrapped in ApiResponse object
                return Ok(ApiResponse<object>.SuccessResponse(tags, "Tags fetched successfully", 200));
            }
            catch (Exception ex)
            {
                // Log: Unexpected error handling
                _logger.LogError(ex, "Error occurred while retrieving document tags for tenant {TenantId}.", tenantId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("An error occurred while fetching tags.", 500));
            }
        }


        /// <summary>
        /// Uploads a document attachment for an Employee or Project.
        /// Validates permissions, document type, entity existence, tags, and uploads to S3.
        /// </summary>

        [HttpPost("upload")]
        public async Task<IActionResult> UploadDocumentAsync([FromBody] DocumentAttachmentDto model)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScope.CreateScope();

            _logger.LogInfo("Document upload initiated for EntityId: {EntityId}, DocumentTypeId: {DocumentTypeId}", model.EntityId, model.DocumentTypeId);

            try
            {
                // Get logged in user
                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

                // Validate Document Type
                var documentType = await _context.DocumentTypeMasters
                    .Include(dt => dt.DocumentCategory)
                    .FirstOrDefaultAsync(dt => dt.Id == model.DocumentTypeId && dt.TenantId == tenantId && dt.DocumentCategory != null);

                if (documentType == null)
                {
                    _logger.LogWarning("DocumentTypeId {DocumentTypeId} not found for Tenant {TenantId}", model.DocumentTypeId, tenantId);
                    return NotFound(ApiResponse<object>.ErrorResponse("Document Type not found", "Document Type not found in database", 404));
                }

                // Permission check
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                var hasUploadPermission = false;
                if (ProjectEntity == documentType.DocumentCategory?.EntityTypeId)
                {
                    hasUploadPermission = await _permission.HasPermission(PermissionsMaster.UploadDocument, loggedInEmployee.Id, model.EntityId);
                }
                else if (EmployeeEntity == documentType.DocumentCategory?.EntityTypeId)
                {
                    hasUploadPermission = await _permission.HasPermission(PermissionsMaster.UploadDocument, loggedInEmployee.Id);
                }

                if (!hasUploadPermission && loggedInEmployee.Id != model.EntityId)
                {
                    _logger.LogWarning("Access Denied. User {UserId} tried to upload document for {EntityId}", loggedInEmployee.Id, model.EntityId);
                    return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to upload this document", 403));
                }

                // Document ID validation
                if (documentType.IsMandatory && string.IsNullOrWhiteSpace(model.DocumentId))
                {
                    _logger.LogWarning("Mandatory DocumentId missing for DocumentTypeId: {DocumentTypeId}", documentType.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Document ID missing", "User must provide the document ID for this document", 400));
                }

                if (documentType.IsValidationRequired && !string.IsNullOrWhiteSpace(model.DocumentId) && !string.IsNullOrWhiteSpace(documentType.RegexExpression))
                {
                    if (!Regex.IsMatch(model.DocumentId, documentType.RegexExpression))
                    {
                        _logger.LogWarning("Invalid DocumentId format for DocumentTypeId: {DocumentTypeId}, Provided: {DocumentId}", documentType.Id, model.DocumentId);
                        return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Document ID", "Provided document ID is not valid", 400));
                    }
                }

                // Verify if Employee/Project exists
                var entityType = documentType.DocumentCategory!.EntityTypeId;

                bool entityExists = false;
                if (entityType.Equals(EmployeeEntity))
                {
                    entityExists = await _context.Employees.AnyAsync(e => e.Id == model.EntityId && e.TenantId == tenantId);
                }
                else if (entityType.Equals(ProjectEntity))
                {
                    entityExists = await _context.Projects.AnyAsync(p => p.Id == model.EntityId && p.TenantId == tenantId);
                }

                if (!entityExists)
                {
                    _logger.LogWarning("Entity not found. EntityType: {EntityType}, EntityId: {EntityId}", entityType, model.EntityId);
                    return NotFound(ApiResponse<object>.ErrorResponse($"{(entityType == EmployeeEntity ? "Employee" : "Project")} Not Found", "Entity not found in database", 404));
                }

                // Map DTO to DB entity
                var attachment = _mapper.Map<DocumentAttachment>(model);
                attachment.UploadedAt = DateTime.UtcNow;
                attachment.UploadedById = loggedInEmployee.Id;
                attachment.IsCurrentVersion = true;
                attachment.TenantId = tenantId;

                // Validate Attachment
                var allowedSize = documentType.MaxSizeAllowedInMB * 1024;
                if (model.Attachment.FileSize > allowedSize)
                {
                    _logger.LogWarning("File size {FileSize} exceeded max allowed {MaxSize}MB", model.Attachment.FileSize, documentType.MaxSizeAllowedInMB);
                    return BadRequest(ApiResponse<object>.ErrorResponse("File size limit exceeded", $"Max allowed {documentType.MaxSizeAllowedInMB} MB.", 400));
                }

                string base64 = model.Attachment.Base64Data?.Split(',').LastOrDefault() ?? "";
                if (string.IsNullOrWhiteSpace(base64))
                {
                    _logger.LogWarning("Missing Base64 data for attachment.");
                    return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data missing", "File data required", 400));
                }

                var s3Service = scope.ServiceProvider.GetRequiredService<S3UploadService>();
                var fileType = s3Service.GetContentTypeFromBase64(base64);

                var validContentTypes = documentType.AllowedContentType.Split(',').ToList();
                if (!validContentTypes.Contains(fileType))
                {
                    _logger.LogWarning("Unsupported file type {FileType} for DocumentType {DocumentTypeId}", fileType, documentType.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Unsupported file type", $"Unsupported file type: {fileType}", 400));
                }

                // Generate S3 ObjectKey/FileName
                string folderName = entityType == EmployeeEntity ? "EmployeeDocuments" : "ProjectDocuments";
                string fileName = s3Service.GenerateFileName(fileType, tenantId, folderName);
                string objectKey = entityType == EmployeeEntity
                    ? $"tenant-{tenantId}/Employee/{model.EntityId}/{folderName}/{fileName}"
                    : $"tenant-{tenantId}/project-{model.EntityId}/{folderName}/{fileName}";

                // Fire-and-forget upload
                _ = Task.Run(async () =>
                {
                    var logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
                    try
                    {
                        await s3Service.UploadFileAsync(base64, fileType, objectKey);
                        logger.LogInfo("File uploaded successfully to S3: {ObjectKey}", objectKey);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "S3 upload failed for {ObjectKey}.", objectKey);
                    }
                });

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

                _context.Documents.Add(document);

                attachment.DocumentDataId = document.Id;

                _context.DocumentAttachments.Add(attachment);

                //Process Versioning

                var versionMapping = new AttachmentVersionMapping
                {
                    ParentAttachmentId = attachment.Id,
                    ChildAttachmentId = attachment.Id,
                    Version = 1,
                    TenantId = tenantId
                };
                _context.AttachmentVersionMappings.Add(versionMapping);

                // Process Tags
                if (model.Tags?.Any() == true)
                {
                    var names = model.Tags.Select(t => t.Name).ToList();
                    var existingTags = await _context.DocumentTagMasters
                        .Where(t => names.Contains(t.Name) && t.TenantId == tenantId)
                        .ToListAsync();

                    var attachmentTagMappings = new List<AttachmentTagMapping>();

                    foreach (var tag in model.Tags.Where(t => t.IsActive))
                    {
                        var existingTag = existingTags.FirstOrDefault(t => t.Name == tag.Name);
                        var tagEntity = existingTag ?? new DocumentTagMaster
                        {
                            Id = Guid.NewGuid(),
                            Name = tag.Name,
                            Description = tag.Name,
                            TenantId = tenantId
                        };

                        if (existingTag == null)
                        {
                            _context.DocumentTagMasters.Add(tagEntity);
                        }

                        attachmentTagMappings.Add(new AttachmentTagMapping
                        {
                            DocumentTagId = tagEntity.Id,
                            AttachmentId = attachment.Id,
                            TenantId = tenantId
                        });
                    }

                    _context.AttachmentTagMappings.AddRange(attachmentTagMappings);
                }


                await _context.SaveChangesAsync();

                _logger.LogInfo("Document uploaded successfully. AttachmentId: {AttachmentId}, DocumentId: {DocumentId}", attachment.Id, document.Id);

                var response = _mapper.Map<DocumentListVM>(attachment);
                response.UploadedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
                response.ParentAttachmentId = versionMapping.ParentAttachmentId;
                response.Version = versionMapping.Version;

                var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

                _ = Task.Run(async () =>
                {
                    // --- Push Notification Section ---
                    // This section attempts to send a test push notification to the user's device.
                    // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    if (EmployeeEntity == entityType && model.EntityId != loggedInEmployee.Id)
                    {
                        var notification = new Notification
                        {
                            Title = $"Document added to Employee.",
                            Body = $"Document added to your profile of type \"{documentType.Name}\" by {name}"
                        };

                        await _firebase.SendEmployeeDocumentMessageAsync(attachment.Id, model.EntityId, notification, tenantId);
                    }
                    if (ProjectEntity == entityType)
                    {
                        var notification = new Notification
                        {
                            Title = $"Document added to Project.",
                            Body = $"Document added to your Project of type \"{documentType.Name}\" by {name}"
                        };

                        await _firebase.SendProjectDocumentMessageAsync(attachment.Id, model.EntityId, notification, tenantId);
                    }

                });

                return Ok(ApiResponse<object>.SuccessResponse(response, "Document added successfully", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unexpected error during document upload.");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error", "An error occurred while uploading the document", 500));
            }
        }

        /// <summary>
        /// Verifies a document attachment by its ID. Checks permissions, logs the operation, and updates verification fields.
        /// </summary>
        /// <param name="id">Document Attachment ID (Guid)</param>
        /// <param name="isVerify">Flag to verify or unverify the document (default: true)</param>

        [HttpPost("verify/{id}")]
        public async Task<IActionResult> VerifyDocumentAsync(Guid id, [FromQuery] bool isVerify = true)
        {
            // Begin: Create DbContext and DI scope
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScope.CreateScope();

            try
            {
                // Get current logged-in employee for authentication/auditing
                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

                _logger.LogInfo("Attempting to verify document. EmployeeId: {EmployeeId}, DocumentId: {DocumentId}, IsVerify: {IsVerify}",
                                        loggedInEmployee.Id, id, isVerify);

                // Fetch active/current document by Id, TenantId, and relevant conditions
                var documentAttachment = await _context.DocumentAttachments
                    .Include(da => da.DocumentType)
                        .ThenInclude(dt => dt!.DocumentCategory)
                    .FirstOrDefaultAsync(da => da.Id == id && da.IsActive && da.IsCurrentVersion && da.TenantId == tenantId);

                if (documentAttachment == null)
                {
                    _logger.LogWarning("Document attachment not found. Requested Id: {DocumentId}, TenantId: {TenantId}", id, tenantId);
                    return NotFound(ApiResponse<object>.ErrorResponse("Attachment not found", "Attachment not found in database", 404));
                }

                // Permission service: check if employee is authorized to verify documents
                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                var hasVerifyPermission = false;
                if (ProjectEntity == documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId)
                {
                    hasVerifyPermission = await _permission.HasPermission(PermissionsMaster.VerifyDocument, loggedInEmployee.Id, documentAttachment.EntityId);
                }
                else if (EmployeeEntity == documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId)
                {
                    hasVerifyPermission = await _permission.HasPermission(PermissionsMaster.VerifyDocument, loggedInEmployee.Id);
                }
                if (!hasVerifyPermission)
                {
                    _logger.LogWarning("Access denied for document verification. EmployeeId: {EmployeeId}, DocumentId: {DocumentId}", loggedInEmployee.Id, id);
                    return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to verify this document", 403));
                }

                // Log existing entity state before update (for audit trail)
                var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
                var existingEntityBson = updateLogHelper.EntityToBsonDocument(documentAttachment);

                // Update document verification status and audit fields
                documentAttachment.IsVerified = isVerify;
                documentAttachment.VerifiedAt = DateTime.UtcNow;
                documentAttachment.VerifiedById = loggedInEmployee.Id;

                // Commit changes
                await _context.SaveChangesAsync();

                // Log the update to MongoDB for change tracking
                await updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
                {
                    EntityId = documentAttachment.Id.ToString(),
                    UpdatedById = loggedInEmployee.Id.ToString(),
                    OldObject = existingEntityBson,
                    UpdatedAt = DateTime.UtcNow
                }, Collection);

                var versionMapping = await _context.AttachmentVersionMappings.FirstOrDefaultAsync(av => av.ChildAttachmentId == documentAttachment.Id);

                var response = _mapper.Map<DocumentListVM>(documentAttachment);
                if (versionMapping != null)
                {
                    response.ParentAttachmentId = versionMapping.ParentAttachmentId;
                    response.Version = versionMapping.Version;
                }

                _logger.LogInfo("Document verified successfully. DocumentId: {DocumentId}, VerifiedBy: {EmployeeId}", documentAttachment.Id, loggedInEmployee.Id);

                var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
                _ = Task.Run(async () =>
                {
                    // --- Push Notification Section ---
                    // This section attempts to send a test push notification to the user's device.
                    // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    var entityType = documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId;
                    var documentType = documentAttachment.DocumentType;

                    if (EmployeeEntity == entityType && documentAttachment.EntityId != loggedInEmployee.Id)
                    {
                        var notification = new Notification
                        {
                            Title = $"Your Document is Verified.",
                            Body = $"Your Document of type \"{documentType?.Name}\" is Verified by {name}"
                        };

                        await _firebase.SendEmployeeDocumentMessageAsync(documentAttachment.Id, documentAttachment.EntityId, notification, tenantId);
                    }
                    if (ProjectEntity == entityType)
                    {
                        var notification = new Notification
                        {
                            Title = $"Document for your project is verified",
                            Body = $"Document for your Project of type \"{documentType?.Name}\" is Verified by {name}"
                        };

                        await _firebase.SendProjectDocumentMessageAsync(documentAttachment.Id, documentAttachment.EntityId, notification, tenantId);
                    }

                });
                return Ok(ApiResponse<object>.SuccessResponse(new { }, "Document is verified successfully", 200));
            }
            catch (Exception ex)
            {
                // Handle unexpected errors gracefully
                _logger.LogError(ex, "Error occurred during document verification. DocumentId: {DocumentId}", id);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Server Error", "An error occurred while verifying the document", 500));
            }
        }


        [HttpPut("edit/{id}")]
        public async Task<IActionResult> UpdateDocumentAsync(Guid id, [FromBody] UpdateDocumentAttachmentDto model)
        {
            // Logger initialization at the start for consistent logging
            using var scope = _serviceScope.CreateScope();
            _logger.LogInfo("Start UpdateDocument API for AttachmentId: {AttachmentId}", id);

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

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

                // Fetch the existing attachment
                var oldAttachment = await _context.DocumentAttachments
                    .Include(da => da.DocumentType)
                        .ThenInclude(dt => dt!.DocumentCategory)
                    .FirstOrDefaultAsync(da => da.Id == id && da.IsActive && da.IsCurrentVersion && da.TenantId == tenantId);

                if (oldAttachment == null)
                {
                    _logger.LogWarning("Attachment not found for Id: {AttachmentId}", id);
                    return NotFound(ApiResponse<object>.ErrorResponse("Attachment not found", "Attachment not found in database", 404));
                }

                var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                var hasUpdatePermission = false;
                if (ProjectEntity == oldAttachment.DocumentType?.DocumentCategory?.EntityTypeId)
                {
                    hasUpdatePermission = await _permission.HasPermission(PermissionsMaster.UploadDocument, loggedInEmployee.Id, oldAttachment.EntityId);
                }
                else if (EmployeeEntity == oldAttachment.DocumentType?.DocumentCategory?.EntityTypeId)
                {
                    hasUpdatePermission = await _permission.HasPermission(PermissionsMaster.UploadDocument, loggedInEmployee.Id);
                }

                // Permission check: ensure uploader is authorized
                if (!hasUpdatePermission && loggedInEmployee.Id != oldAttachment.EntityId)
                {
                    _logger.LogWarning("Access denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id);
                    return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to upload this document", 403));
                }

                // Validate the document type
                var documentType = oldAttachment.DocumentType;
                if (documentType == null)
                {
                    _logger.LogWarning("Document type not found for AttachmentId: {AttachmentId}", id);
                    return NotFound(ApiResponse<object>.ErrorResponse("Document Type not found", "Document Type not found in database", 404));
                }

                // Mandatory DocumentID check
                if (documentType.IsMandatory && string.IsNullOrWhiteSpace(model.DocumentId))
                {
                    _logger.LogWarning("Document ID missing for mandatory DocumentTypeId: {DocumentTypeId}", documentType.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Document ID missing", "User must provide the document ID for this document", 400));
                }

                // DocumentID Regex validation
                if (documentType.IsValidationRequired && !string.IsNullOrWhiteSpace(model.DocumentId) && !string.IsNullOrWhiteSpace(documentType.RegexExpression))
                {
                    if (!Regex.IsMatch(model.DocumentId, documentType.RegexExpression))
                    {
                        _logger.LogWarning("Provided document ID does not match regex for DocumentTypeId: {DocumentTypeId}", documentType.Id);
                        return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Document ID", "Provided document ID is not valid", 400));
                    }
                }

                // Validate entity existence and project-level permission
                var entityType = documentType.DocumentCategory!.EntityTypeId;
                bool entityExists;
                if (entityType.Equals(EmployeeEntity))
                {
                    entityExists = await _context.Employees.AnyAsync(e => e.Id == oldAttachment.EntityId && e.TenantId == tenantId);
                }
                else if (entityType.Equals(ProjectEntity))
                {
                    entityExists = await _context.Projects.AnyAsync(p => p.Id == oldAttachment.EntityId && p.TenantId == tenantId);
                    if (entityExists)
                    {
                        entityExists = await _permission.HasProjectPermission(loggedInEmployee, oldAttachment.EntityId);
                    }
                }
                else
                {
                    entityExists = false;
                }

                if (!entityExists)
                {
                    _logger.LogWarning("Entity not found (Employee/Project) for AttachmentId: {AttachmentId}", id);
                    return NotFound(ApiResponse<object>.ErrorResponse($"{(entityType == EmployeeEntity ? "Employee" : "Project")} Not Found", "Entity not found in database", 404));
                }

                // Prepare for versioning
                var oldVersionMapping = await _context.AttachmentVersionMappings
                    .FirstOrDefaultAsync(av => av.ChildAttachmentId == oldAttachment.Id && av.TenantId == tenantId);

                var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
                var existingEntityBson = updateLogHelper.EntityToBsonDocument(oldAttachment);

                DocumentAttachment newAttachment;
                AttachmentVersionMapping newVersionMapping;

                if (model.Attachment != null)
                {
                    // File size check
                    var allowedSize = documentType.MaxSizeAllowedInMB * 1024;
                    if (model.Attachment.FileSize > allowedSize)
                    {
                        _logger.LogWarning("Attachment exceeded max file size for DocumentTypeId: {DocumentTypeId}", documentType.Id);
                        return BadRequest(ApiResponse<object>.ErrorResponse("File size limit exceeded", $"Max allowed {documentType.MaxSizeAllowedInMB} MB.", 400));
                    }

                    // Base64 validation
                    string base64 = model.Attachment.Base64Data?.Split(',').LastOrDefault() ?? "";
                    if (string.IsNullOrWhiteSpace(base64))
                    {
                        _logger.LogWarning("Base64 data missing for new attachment");
                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data missing", "File data required", 400));
                    }

                    // Content type verification
                    var s3Service = scope.ServiceProvider.GetRequiredService<S3UploadService>();
                    var fileType = s3Service.GetContentTypeFromBase64(base64);
                    var validContentTypes = documentType.AllowedContentType.Split(',').ToList();
                    if (!validContentTypes.Contains(fileType))
                    {
                        _logger.LogWarning("Unsupported file type: {FileType}", fileType);
                        return BadRequest(ApiResponse<object>.ErrorResponse("Unsupported file type", $"Unsupported file type: {fileType}", 400));
                    }

                    // S3 keys and folder structure
                    string folderName = entityType == EmployeeEntity ? "EmployeeDocuments" : "ProjectDocuments";
                    string fileName = s3Service.GenerateFileName(fileType, tenantId, folderName);
                    string objectKey = entityType == EmployeeEntity
                        ? $"tenant-{tenantId}/Employee/{oldAttachment.EntityId}/{folderName}/{fileName}"
                        : $"tenant-{tenantId}/project-{oldAttachment.EntityId}/{folderName}/{fileName}";

                    // Asynchronous S3 upload with logging
                    _ = Task.Run(async () =>
                    {
                        var logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
                        try
                        {
                            await s3Service.UploadFileAsync(base64, fileType, objectKey);
                            logger.LogInfo("File uploaded successfully to S3: {ObjectKey}", objectKey);
                        }
                        catch (Exception ex)
                        {
                            logger.LogError(ex, "S3 upload failed for {ObjectKey}.", objectKey);
                        }
                    });

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

                    _context.Documents.Add(document);

                    if (oldAttachment.IsVerified == true)
                    {
                        // Record new document attachment as the current version
                        var attachment = new DocumentAttachment
                        {
                            Name = model.Name,
                            DocumentId = model.DocumentId,
                            Description = model.Description,
                            IsCurrentVersion = true,
                            EntityId = oldAttachment.EntityId,
                            DocumentDataId = document.Id,
                            UploadedAt = DateTime.UtcNow,
                            UploadedById = loggedInEmployee.Id,
                            DocumentTypeId = oldAttachment.DocumentTypeId,
                            TenantId = oldAttachment.TenantId
                        };

                        _context.DocumentAttachments.Add(attachment);

                        // Mark old version as not current
                        oldAttachment.IsCurrentVersion = false;

                        // Version mapping
                        AttachmentVersionMapping versionMapping;
                        if (oldVersionMapping != null)
                        {
                            versionMapping = new AttachmentVersionMapping
                            {
                                ParentAttachmentId = oldVersionMapping.ParentAttachmentId,
                                ChildAttachmentId = attachment.Id,
                                Version = (oldVersionMapping.Version + 1),
                                TenantId = tenantId
                            };
                        }
                        else
                        {
                            versionMapping = new AttachmentVersionMapping
                            {
                                ParentAttachmentId = attachment.Id,
                                ChildAttachmentId = attachment.Id,
                                Version = 1,
                                TenantId = tenantId
                            };
                        }
                        _context.AttachmentVersionMappings.Add(versionMapping);

                        newAttachment = attachment;
                        newVersionMapping = versionMapping;
                        _logger.LogInfo("Created new current version for AttachmentId: {AttachmentId}", attachment.Id);
                    }
                    else
                    {
                        oldAttachment.Name = model.Name;
                        oldAttachment.DocumentId = model.DocumentId;
                        oldAttachment.Description = model.Description;
                        oldAttachment.DocumentDataId = document.Id;
                        if (oldAttachment.IsVerified != null)
                        {
                            oldAttachment.IsVerified = null;
                            _logger.LogInfo("Reset verification flag for AttachmentId: {AttachmentId}", oldAttachment.Id);
                        }
                        oldAttachment.UpdatedAt = DateTime.UtcNow;
                        oldAttachment.UpdatedById = loggedInEmployee.Id;

                        newAttachment = oldAttachment;
                        newVersionMapping = oldVersionMapping ?? new AttachmentVersionMapping();
                        _logger.LogInfo("Attachment metadata updated for AttachmentId: {AttachmentId}", oldAttachment.Id);
                    }

                }
                else
                {
                    // Update attachment metadata only (no file upload)
                    oldAttachment.Name = model.Name;
                    oldAttachment.DocumentId = model.DocumentId;
                    oldAttachment.Description = model.Description;
                    if (oldAttachment.IsVerified != null)
                    {
                        oldAttachment.IsVerified = null;
                        _logger.LogInfo("Reset verification flag for AttachmentId: {AttachmentId}", oldAttachment.Id);
                    }
                    oldAttachment.UpdatedAt = DateTime.UtcNow;
                    oldAttachment.UpdatedById = loggedInEmployee.Id;

                    newAttachment = oldAttachment;
                    newVersionMapping = oldVersionMapping ?? new AttachmentVersionMapping();
                    _logger.LogInfo("Attachment metadata updated for AttachmentId: {AttachmentId}", oldAttachment.Id);
                }

                // Tag management
                if (model.Tags?.Any() == true)
                {
                    var names = model.Tags.Select(t => t.Name).ToList();
                    var existingTags = await _context.DocumentTagMasters
                        .Where(t => names.Contains(t.Name) && t.TenantId == tenantId)
                        .ToListAsync();

                    var attachmentTagMappings = new List<AttachmentTagMapping>();
                    var oldTags = await _context.AttachmentTagMappings
                        .Include(dt => dt.DocumentTag)
                        .Where(dt => dt.DocumentTag != null && dt.AttachmentId == newAttachment.Id && dt.TenantId == tenantId)
                        .ToListAsync();

                    var oldTagNames = oldTags.Select(dt => dt.DocumentTag!.Name).ToList();

                    foreach (var tag in model.Tags.Where(t => t.IsActive && !oldTagNames.Contains(t.Name)))
                    {
                        var existingTag = existingTags.FirstOrDefault(t => t.Name == tag.Name);
                        var tagEntity = existingTag ?? new DocumentTagMaster
                        {
                            Id = Guid.NewGuid(),
                            Name = tag.Name,
                            Description = tag.Name,
                            TenantId = tenantId
                        };

                        if (existingTag == null)
                        {
                            _context.DocumentTagMasters.Add(tagEntity);
                        }

                        attachmentTagMappings.Add(new AttachmentTagMapping
                        {
                            DocumentTagId = tagEntity.Id,
                            AttachmentId = newAttachment.Id,
                            TenantId = tenantId
                        });
                    }

                    _context.AttachmentTagMappings.AddRange(attachmentTagMappings);

                    var deletedTagMappings = new List<AttachmentTagMapping>();

                    foreach (var tag in model.Tags.Where(t => !t.IsActive && oldTagNames.Contains(t.Name)))
                    {
                        var deletedTagMapping = oldTags.FirstOrDefault(at => at.DocumentTag!.Name == tag.Name);

                        if (deletedTagMapping != null)
                        {
                            deletedTagMappings.Add(deletedTagMapping);
                        }
                    }
                    _context.AttachmentTagMappings.RemoveRange(deletedTagMappings);

                    _logger.LogInfo("Tags processed for AttachmentId: {AttachmentId}", newAttachment.Id);
                }

                // Persist changes to database
                await _context.SaveChangesAsync();
                _logger.LogInfo("Database changes committed for AttachmentId: {AttachmentId}", newAttachment.Id);

                // Update logs
                await updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
                {
                    EntityId = oldAttachment.Id.ToString(),
                    UpdatedById = loggedInEmployee.Id.ToString(),
                    OldObject = existingEntityBson,
                    UpdatedAt = DateTime.UtcNow
                }, Collection);
                _logger.LogInfo("Update logs pushed for AttachmentId: {AttachmentId}", oldAttachment.Id);

                // Prepare response
                var response = _mapper.Map<DocumentListVM>(newAttachment);
                response.UploadedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
                response.ParentAttachmentId = newVersionMapping.ParentAttachmentId;
                response.Version = newVersionMapping.Version;
                _logger.LogInfo("API completed successfully for AttachmentId: {AttachmentId}", newAttachment.Id);

                var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

                _ = Task.Run(async () =>
                {
                    // --- Push Notification Section ---
                    // This section attempts to send a test push notification to the user's device.
                    // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    if (EmployeeEntity == entityType && newAttachment.EntityId != loggedInEmployee.Id)
                    {
                        var notification = new Notification
                        {
                            Title = $"Your Document is updated.",
                            Body = $"Your Document of type \"{documentType?.Name}\" is updated by {name}"
                        };

                        await _firebase.SendEmployeeDocumentMessageAsync(newAttachment.Id, newAttachment.EntityId, notification, tenantId);
                    }
                    if (ProjectEntity == entityType)
                    {
                        var notification = new Notification
                        {
                            Title = "Your Project Document is updated.",
                            Body = $"Your Project Document of type \"{documentType?.Name}\" is updated by {name}"
                        };

                        await _firebase.SendProjectDocumentMessageAsync(newAttachment.Id, newAttachment.EntityId, notification, tenantId);
                    }

                });

                return Ok(ApiResponse<object>.SuccessResponse(response, "Document Updated successfully", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occurred while updating document for AttachmentId: {AttachmentId}", id);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Exception occured", "Exception occured while the updating document", 500));
            }
        }

        [HttpDelete("delete/{id}")]
        public async Task<IActionResult> DeleteDocumentAsync(Guid id, [FromQuery] bool isActive = false)
        {
            // Create a new DbContext instance asynchronously
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Create a new service scope to resolve scoped services like permission and logging helpers
            using var scope = _serviceScope.CreateScope();
            var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
            var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();

            // Message to indicate whether the document is being activated or deactivated
            var message = isActive ? "activated" : "deactivated";

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

            // Log the start of the delete operation for traceability
            _logger.LogInfo("DeleteDocument started for document ID: {DocumentId} by employee ID: {EmployeeId}", id, loggedInEmployee.Id);

            // Retrieve the document attachment matching the criteria from the database
            var documentAttachment = await _context.DocumentAttachments
                .Include(da => da.DocumentType)
                    .ThenInclude(dt => dt!.DocumentCategory)
                .FirstOrDefaultAsync(da => da.Id == id && da.IsCurrentVersion && da.TenantId == tenantId && da.IsActive != isActive);

            // If the document attachment is not found, log a warning and return 404 Not Found
            if (documentAttachment == null)
            {
                _logger.LogWarning("DocumentAttachment not found for ID: {DocumentId}", id);
                return NotFound(ApiResponse<object>.ErrorResponse("Document Attachment not found", "Document Attachment not found in database", 404));
            }

            // Check if the logged in employee has permission to delete OR is the owner of the document attachment
            var hasDeletePermission = false;
            if (ProjectEntity == documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId)
            {
                hasDeletePermission = await _permission.HasPermission(PermissionsMaster.DeleteDocument, loggedInEmployee.Id, documentAttachment.EntityId);
            }
            else if (EmployeeEntity == documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId)
            {
                hasDeletePermission = await _permission.HasPermission(PermissionsMaster.DeleteDocument, loggedInEmployee.Id);
            }
            if (!hasDeletePermission && loggedInEmployee.Id != documentAttachment.EntityId)
            {
                _logger.LogWarning("Access denied for employee ID: {EmployeeId} when attempting to delete document ID: {DocumentId}", loggedInEmployee.Id, id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to delete documents", 403));
            }

            // Log the current state of the document attachment before updating for audit trail
            var existingEntityBson = updateLogHelper.EntityToBsonDocument(documentAttachment);

            // Update document attachment status and metadata
            documentAttachment.IsActive = isActive;
            documentAttachment.IsVerified = null;
            documentAttachment.UpdatedAt = DateTime.UtcNow;
            documentAttachment.UpdatedById = loggedInEmployee.Id;

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

            // Log the update operation to MongoDB for inspection and history
            await updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
            {
                EntityId = documentAttachment.Id.ToString(),
                UpdatedById = loggedInEmployee.Id.ToString(),
                OldObject = existingEntityBson,
                UpdatedAt = DateTime.UtcNow
            }, Collection);

            // Log the successful completion of the operation
            _logger.LogInfo("DocumentAttachment ID: {DocumentId} has been {Message} by employee ID: {EmployeeId}", id, message, loggedInEmployee.Id);

            var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

            _ = Task.Run(async () =>
            {
                // --- Push Notification Section ---
                // This section attempts to send a test push notification to the user's device.
                // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                var entityType = documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId;
                var documentType = documentAttachment.DocumentType;

                var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                if (EmployeeEntity == entityType && documentAttachment.EntityId != loggedInEmployee.Id)
                {
                    var notification = new Notification
                    {
                        Title = $"Your Document is {message}.",
                        Body = $"Your Document of type \"{documentType?.Name}\" is {message} by {name}"
                    };

                    await _firebase.SendEmployeeDocumentMessageAsync(documentAttachment.Id, documentAttachment.EntityId, notification, tenantId);
                }
                if (ProjectEntity == entityType)
                {
                    var notification = new Notification
                    {
                        Title = "Your Project Document is {message}.",
                        Body = $"Your Project Document of type \"{documentType?.Name}\" is {message} by {name}"
                    };

                    await _firebase.SendProjectDocumentMessageAsync(documentAttachment.Id, documentAttachment.EntityId, notification, tenantId);
                }

            });

            // Return success response
            return Ok(ApiResponse<object>.SuccessResponse(new { }, $"Document attachment is {message}", 200));
        }


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

        private DocumentFilter? TryDeserializeFilter(string? filter)
        {
            if (string.IsNullOrWhiteSpace(filter))
            {
                return null;
            }

            var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
            DocumentFilter? documentFilter = null;

            try
            {
                // First, try to deserialize directly. This is the expected case (e.g., from a web client).
                documentFilter = JsonSerializer.Deserialize<DocumentFilter>(filter, options);
            }
            catch (JsonException ex)
            {
                _logger.LogError(ex, "[{MethodName}] Failed to directly deserialize filter. Attempting to unescape and re-parse. Filter: {Filter}", nameof(TryDeserializeFilter), filter);

                // If direct deserialization fails, it might be an escaped string (common with tools like Postman or some mobile clients).
                try
                {
                    // Unescape the string first, then deserialize the result.
                    string unescapedJsonString = JsonSerializer.Deserialize<string>(filter, options) ?? "";
                    if (!string.IsNullOrWhiteSpace(unescapedJsonString))
                    {
                        documentFilter = JsonSerializer.Deserialize<DocumentFilter>(unescapedJsonString, options);
                    }
                }
                catch (JsonException ex1)
                {
                    // If both attempts fail, log the final error and return null.
                    _logger.LogError(ex1, "[{MethodName}] All attempts to deserialize the filter failed. Filter will be ignored. Filter: {Filter}", nameof(TryDeserializeFilter), filter);
                    return null;
                }
            }
            return documentFilter;
        }

        #endregion
    }
}
