using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Activities;
using Marco.Pms.Model.Dtos.Activities;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Filters;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Hubs;
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.AspNetCore.SignalR;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
using Document = Marco.Pms.Model.DocumentManager.Document;

namespace MarcoBMS.Services.Controllers
{

    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class TaskController : ControllerBase
    {
        private readonly ApplicationDbContext _context;
        private readonly UserHelper _userHelper;
        private readonly S3UploadService _s3Service;
        private readonly ILoggingService _logger;
        private readonly IHubContext<MarcoHub> _signalR;
        private readonly CacheUpdateHelper _cache;
        private readonly PermissionServices _permissionServices;
        private readonly IFirebaseService _firebase;

        public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices,
            IHubContext<MarcoHub> signalR, CacheUpdateHelper cache, IFirebaseService firebase)
        {
            _context = context;
            _userHelper = userHelper;
            _s3Service = s3Service;
            _logger = logger;
            _signalR = signalR;
            _cache = cache;
            _permissionServices = permissionServices;
            _firebase = firebase;
        }

        private Guid GetTenantId()
        {
            return _userHelper.GetTenantId();
        }

        [HttpPost("assign")]
        public async Task<IActionResult> AssignTask([FromBody] AssignTaskDto assignTask)
        {
            // Validate the incoming model
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values
                    .SelectMany(v => v.Errors)
                    .Select(e => e.ErrorMessage)
                    .ToList();

                _logger.LogWarning("AssignTask failed validation: {@Errors}", errors);
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
            }

            // Retrieve tenant and loggedInEmployee context
            var tenantId = GetTenantId();
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Check for permission to approve tasks
            var hasPermission = await _permissionServices.HasPermission(PermissionsMaster.AssignAndReportProgress, loggedInEmployee.Id);
            if (!hasPermission)
            {
                _logger.LogWarning("Employee {EmployeeId} attempted to assign Task without permission", loggedInEmployee.Id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "User not authorized to approve tasks", 403));
            }

            _logger.LogInfo("Employee {EmployeeId} is assigning a new task", loggedInEmployee.Id);

            // Convert DTO to entity and save TaskAllocation
            var taskAllocation = assignTask.ToTaskAllocationFromAssignTaskDto(loggedInEmployee.Id, tenantId);
            _context.TaskAllocations.Add(taskAllocation);
            await _context.SaveChangesAsync();

            await _cache.UpdatePlannedAndCompleteWorksInWorkItem(taskAllocation.WorkItemId, todaysAssigned: taskAllocation.PlannedTask);

            _logger.LogInfo("Task {TaskId} assigned by Employee {EmployeeId}", taskAllocation.Id, loggedInEmployee.Id);

            var response = taskAllocation.ToAssignTaskVMFromTaskAllocation();

            // Map team members
            var teamMembers = new List<TaskMembers>();
            if (assignTask.TaskTeam != null && assignTask.TaskTeam.Any())
            {
                teamMembers = assignTask.TaskTeam.Select(memberId => new TaskMembers
                {
                    TaskAllocationId = taskAllocation.Id,
                    EmployeeId = memberId,
                    TenantId = tenantId
                }).ToList();

                _context.TaskMembers.AddRange(teamMembers);
                await _context.SaveChangesAsync();

                _logger.LogInfo("Team members added to Task {TaskId}: {@TeamMemberIds}", taskAllocation.Id, assignTask.TaskTeam);
            }

            // Get team member details
            var employeeIds = teamMembers.Select(m => m.EmployeeId).ToList();
            var employees = await _context.Employees
                .Where(e => employeeIds.Contains(e.Id))
                .ToListAsync();

            var team = employees.Select(e => e.ToBasicEmployeeVMFromEmployee()).ToList();
            response.teamMembers = team;

            _ = 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}";

                await _firebase.SendAssignTaskMessageAsync(taskAllocation.WorkItemId, name, employeeIds, tenantId);

            });

            return Ok(ApiResponse<object>.SuccessResponse(response, "Task assigned successfully", 200));
        }

        [HttpPost("report")]
        public async Task<IActionResult> ReportTaskProgress([FromBody] ReportTaskDto reportTask)
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values
                    .SelectMany(v => v.Errors)
                    .Select(e => e.ErrorMessage)
                    .ToList();

                _logger.LogWarning("Task report validation failed: {@Errors}", errors);
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
            }

            var tenantId = GetTenantId();
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            var hasPermission = await _permissionServices.HasPermission(PermissionsMaster.AssignAndReportProgress, loggedInEmployee.Id);
            if (!hasPermission)
            {
                _logger.LogWarning("Unauthorized task report attempt by Employee {EmployeeId} for Task {TaskId}", loggedInEmployee.Id, reportTask.Id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "User not authorized to report tasks", 403));
            }

            var taskAllocation = await _context.TaskAllocations
                .Include(t => t.WorkItem)
                    .ThenInclude(wi => wi!.WorkArea)
                        .ThenInclude(wa => wa!.Floor)
                            .ThenInclude(f => f!.Building)
                .FirstOrDefaultAsync(t => t.Id == reportTask.Id &&
                        t.WorkItem != null &&
                        t.WorkItem.WorkArea != null &&
                        t.WorkItem.WorkArea.Floor != null &&
                        t.WorkItem.WorkArea.Floor.Building != null);

            if (taskAllocation == null)
            {
                _logger.LogWarning("No task allocation found with ID {TaskId}", reportTask.Id);
                return BadRequest(ApiResponse<object>.ErrorResponse("No such task has been allocated.", "No such task has been allocated.", 400));
            }

            var checkListIds = reportTask.CheckList?.Select(c => c.Id).ToList() ?? new List<Guid>();
            var checkList = await _context.ActivityCheckLists
                .Where(c => checkListIds.Contains(c.Id))
                .ToListAsync();

            if (taskAllocation.WorkItem != null)
            {
                if (taskAllocation.CompletedTask > 0)
                    taskAllocation.WorkItem.CompletedWork -= taskAllocation.CompletedTask;

                taskAllocation.WorkItem.CompletedWork += reportTask.CompletedTask;
            }

            taskAllocation.ParentTaskId = reportTask.ParentTaskId;
            taskAllocation.ReportedDate = reportTask.ReportedDate;
            taskAllocation.ReportedById = loggedInEmployee.Id;
            taskAllocation.CompletedTask = reportTask.CompletedTask;
            //taskAllocation.ReportedTask = reportTask.CompletedTask;

            var checkListMappings = new List<CheckListMappings>();
            var checkListVMs = new List<CheckListVM>();

            if (reportTask.CheckList != null)
            {
                var activityId = taskAllocation.WorkItem?.ActivityId ?? Guid.Empty;

                foreach (var checkDto in reportTask.CheckList)
                {
                    checkListVMs.Add(checkDto.ToCheckListVMFromReportCheckListDto(activityId));

                    if (checkDto.IsChecked && checkList.Any(c => c.Id == checkDto.Id))
                    {
                        checkListMappings.Add(new CheckListMappings
                        {
                            CheckListId = checkDto.Id,
                            TaskAllocationId = reportTask.Id
                        });
                    }
                }

                _context.CheckListMappings.AddRange(checkListMappings);
            }

            var comment = reportTask.ToCommentFromReportTaskDto(tenantId, loggedInEmployee.Id);
            _context.TaskComments.Add(comment);

            int numberofImages = 0;

            var workAreaId = taskAllocation.WorkItem?.WorkAreaId;
            var workArea = await _context.WorkAreas.Include(a => a.Floor)
                .FirstOrDefaultAsync(a => a.Id == workAreaId) ?? new WorkArea();

            var buildingId = workArea.Floor?.BuildingId;

            var building = await _context.Buildings
                .FirstOrDefaultAsync(b => b.Id == buildingId);
            var batchId = Guid.NewGuid();
            var projectId = building?.ProjectId;

            if (reportTask.Images?.Any() == true)
            {


                foreach (var image in reportTask.Images)
                {
                    if (string.IsNullOrEmpty(image.Base64Data))
                    {
                        _logger.LogWarning("Image upload failed: Base64 data is missing");
                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
                    }

                    var base64 = image.Base64Data.Contains(',')
                        ? image.Base64Data[(image.Base64Data.IndexOf(",") + 1)..]
                        : image.Base64Data;

                    var fileType = _s3Service.GetContentTypeFromBase64(base64);
                    var fileName = _s3Service.GenerateFileName(fileType, taskAllocation.Id, "task_report");
                    var objectKey = $"tenant-{tenantId}/project-{projectId}/Activity/{fileName}";

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

                    var document = new Document
                    {
                        BatchId = batchId,
                        UploadedById = loggedInEmployee.Id,
                        FileName = image.FileName ?? "",
                        ContentType = image.ContentType ?? "",
                        S3Key = objectKey,
                        //Base64Data = image.Base64Data,
                        FileSize = image.FileSize,
                        UploadedAt = DateTime.UtcNow,
                        TenantId = tenantId
                    };
                    _context.Documents.Add(document);

                    var attachment = new TaskAttachment
                    {
                        DocumentId = document.Id,
                        ReferenceId = reportTask.Id
                    };
                    _context.TaskAttachments.Add(attachment);
                    numberofImages += 1;
                }
            }

            await _context.SaveChangesAsync();
            var selectedWorkAreaId = taskAllocation.WorkItem?.WorkAreaId ?? Guid.Empty;

            await _cache.UpdatePlannedAndCompleteWorksInWorkItem(taskAllocation.WorkItemId, completedWork: taskAllocation.CompletedTask);
            await _cache.UpdatePlannedAndCompleteWorksInBuilding(selectedWorkAreaId, completedWork: taskAllocation.CompletedTask);

            var response = taskAllocation.ToReportTaskVMFromTaskAllocation();
            var comments = await _context.TaskComments
                .Where(c => c.TaskAllocationId == taskAllocation.Id)
                .ToListAsync();

            response.Comments = comments.Select(c => c.ToCommentVMFromTaskComment()).ToList();
            response.checkList = checkListVMs;

            var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Task_Report", NumberOfImages = numberofImages, ProjectId = projectId };
            await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);

            _logger.LogInfo("Task {TaskId} reported successfully by Employee {EmployeeId}", taskAllocation.Id, loggedInEmployee.Id);

            _ = 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}";

                await _firebase.SendReportTaskMessageAsync(taskAllocation.Id, name, tenantId);

            });

            return Ok(ApiResponse<object>.SuccessResponse(response, "Task reported successfully", 200));
        }

        [HttpPost("comment")]
        public async Task<IActionResult> AddCommentForTask([FromBody] CreateCommentDto createComment)
        {
            _logger.LogInfo("AddCommentForTask called for TaskAllocationId: {TaskId}", createComment.TaskAllocationId);

            var tenantId = GetTenantId();
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Validate Task Allocation and associated WorkItem
            var taskAllocation = await _context.TaskAllocations
                .Include(t => t.WorkItem)
                .FirstOrDefaultAsync(t => t.Id == createComment.TaskAllocationId);

            if (taskAllocation == null || taskAllocation.WorkItem == null)
            {
                _logger.LogWarning("Invalid task allocation or work item not found.");
                return BadRequest(ApiResponse<object>.ErrorResponse("No such task has been allocated.", "No such task has been allocated.", 400));
            }

            // Fetch WorkArea and Building (if available)
            var workArea = await _context.WorkAreas
                .Include(a => a.Floor)
                .FirstOrDefaultAsync(a => a.Id == taskAllocation.WorkItem.WorkAreaId) ?? new WorkArea();

            var buildingId = workArea.Floor?.BuildingId ?? Guid.Empty;
            var building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == buildingId);
            var projectId = building?.ProjectId;

            // Save comment
            var comment = createComment.ToCommentFromCommentDto(tenantId, loggedInEmployee.Id);
            _context.TaskComments.Add(comment);
            await _context.SaveChangesAsync();
            _logger.LogInfo("Comment saved with Id: {CommentId}", comment.Id);

            // Process image uploads
            var images = createComment.Images;
            var batchId = Guid.NewGuid();
            int numberofImages = 0;

            if (images != null && images.Any())
            {
                foreach (var image in images)
                {
                    if (string.IsNullOrWhiteSpace(image.Base64Data))
                    {
                        _logger.LogWarning("Missing Base64 data in one of the images.");
                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
                    }

                    // Clean base64 string
                    var base64 = image.Base64Data.Contains(",")
                        ? image.Base64Data.Substring(image.Base64Data.IndexOf(",") + 1)
                        : image.Base64Data;

                    var fileType = _s3Service.GetContentTypeFromBase64(base64);
                    var fileName = _s3Service.GenerateFileName(fileType, comment.Id, "task_comment");
                    var objectKey = $"tenant-{tenantId}/project-{projectId}/Activity/{fileName}";

                    await _s3Service.UploadFileAsync(base64, fileType, objectKey);
                    _logger.LogInfo("Image uploaded to S3 with key: {ObjectKey}", objectKey);

                    var document = new Document
                    {
                        BatchId = batchId,
                        UploadedById = loggedInEmployee.Id,
                        FileName = image.FileName ?? string.Empty,
                        ContentType = image.ContentType ?? fileType,
                        S3Key = objectKey,
                        //Base64Data = image.Base64Data,
                        FileSize = image.FileSize,
                        UploadedAt = DateTime.UtcNow,
                        TenantId = tenantId
                    };

                    _context.Documents.Add(document);

                    var attachment = new TaskAttachment
                    {
                        DocumentId = document.Id,
                        ReferenceId = comment.Id
                    };

                    _context.TaskAttachments.Add(attachment);
                    numberofImages += 1;
                }

                await _context.SaveChangesAsync();
                _logger.LogInfo("Documents and attachments saved for commentId: {CommentId}", comment.Id);
            }

            // Convert to view model and return response
            var response = comment.ToCommentVMFromTaskComment();
            _logger.LogInfo("Returning response for commentId: {CommentId}", comment.Id);

            var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Task_Comment", NumberOfImages = numberofImages, ProjectId = projectId };
            await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);

            _ = 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}";

                await _firebase.SendTaskCommentMessageAsync(taskAllocation.Id, name, tenantId);

            });

            return Ok(ApiResponse<object>.SuccessResponse(response, "Comment saved successfully", 200));
        }

        [HttpGet("list")]
        public async Task<IActionResult> GetTasksList([FromQuery] Guid projectId, [FromQuery] string? filter, [FromQuery] Guid? serviceId,
            [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
        {
            _logger.LogInfo("GetTasksList called for projectId: {ProjectId}", projectId);

            Guid tenantId = GetTenantId();

            // 1. Get task allocations in the specified date range
            var taskAllocationQuery = _context.TaskAllocations
                .Include(t => t.Employee)
                .Include(t => t.ReportedBy)
                .Include(t => t.ApprovedBy)
                .Include(t => t.WorkStatus)
                .Include(t => t.WorkItem)
                    .ThenInclude(wi => wi!.ActivityMaster)
                        .ThenInclude(a => a!.ActivityGroup)
                            .ThenInclude(ag => ag!.Service)
                .Include(t => t.WorkItem)
                    .ThenInclude(wi => wi!.WorkCategoryMaster)
                .Include(t => t.WorkItem)
                    .ThenInclude(wi => wi!.WorkArea)
                        .ThenInclude(wa => wa!.Floor)
                            .ThenInclude(f => f!.Building)
                .Where(t => t.WorkItem != null &&
                            t.WorkItem.WorkArea != null &&
                            t.WorkItem.WorkArea.Floor != null &&
                            t.WorkItem.WorkArea.Floor.Building != null &&
                            t.WorkItem.WorkArea.Floor.Building.ProjectId == projectId &&
                            t.TenantId == tenantId);

            var taskFilter = TryDeserializeFilter(filter);

            if (taskFilter != null)
            {
                if (taskFilter.BuildingIds?.Any() ?? false)
                {
                    taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
                        t.WorkItem.WorkArea != null &&
                        t.WorkItem.WorkArea.Floor != null &&
                        taskFilter.BuildingIds.Contains(t.WorkItem.WorkArea.Floor.BuildingId));
                }
                if (taskFilter.FloorIds?.Any() ?? false)
                {
                    taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
                        t.WorkItem.WorkArea != null &&
                        taskFilter.FloorIds.Contains(t.WorkItem.WorkArea.FloorId));
                }
                if (taskFilter.ActivityIds?.Any() ?? false)
                {
                    taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
                        taskFilter.ActivityIds.Contains(t.WorkItem.ActivityId));
                }
                if (taskFilter.ServiceIds?.Any() ?? false)
                {
                    taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
                    t.WorkItem.ActivityMaster != null &&
                    t.WorkItem.ActivityMaster.ActivityGroup != null &&
                    taskFilter.ServiceIds.Contains(t.WorkItem.ActivityMaster.ActivityGroup.ServiceId));
                }
                if (taskFilter.dateFrom.HasValue && taskFilter.dateTo.HasValue)
                {
                    taskAllocationQuery = taskAllocationQuery.Where(t => t.AssignmentDate.Date >= taskFilter.dateFrom.Value.Date &&
                            t.AssignmentDate.Date <= taskFilter.dateTo.Value.Date);
                }
            }

            if (serviceId.HasValue)
            {
                taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
                    t.WorkItem.ActivityMaster != null &&
                    t.WorkItem.ActivityMaster.ActivityGroup != null &&
                    t.WorkItem.ActivityMaster.ActivityGroup.ServiceId == serviceId);
            }


            int totalRecords = await taskAllocationQuery.CountAsync();
            int totalPages = (int)Math.Ceiling((double)totalRecords / pageSize);

            var taskAllocations = await taskAllocationQuery
                .OrderByDescending(t => t.AssignmentDate)
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize)
                .ToListAsync();

            var taskIds = taskAllocations.Select(t => t.Id).ToList();

            // 2. Load team members
            _logger.LogInfo("Loading task members and related employee data.");
            var teamMembers = await _context.TaskMembers
                .Include(t => t.Employee)
                .Where(t => taskIds.Contains(t.TaskAllocationId))
                .ToListAsync();

            // 3. Load task comments
            _logger.LogInfo("Fetching comments and attachments.");
            var allComments = await _context.TaskComments
                .Include(c => c.Employee)
                .Where(c => taskIds.Contains(c.TaskAllocationId))
                .ToListAsync();
            var commentIds = allComments.Select(c => c.Id).ToList();

            // 4. Load all attachments (task and comment)
            var attachments = await _context.TaskAttachments
                .Where(t => taskIds.Contains(t.ReferenceId) || commentIds.Contains(t.ReferenceId))
                .ToListAsync();

            var documentIds = attachments.Select(t => t.DocumentId).ToList();

            // 5. Load actual documents from attachment references
            var documents = await _context.Documents
                .Where(d => documentIds.Contains(d.Id))
                .ToListAsync();

            var tasks = new List<ListTaskVM>();

            _logger.LogInfo("Constructing task response data.");


            foreach (var taskAllocation in taskAllocations)
            {
                var response = taskAllocation.ToListTaskVMFromTaskAllocation();

                // Attach documents to the main task
                var taskDocIds = attachments
                    .Where(a => a.ReferenceId == taskAllocation.Id)
                    .Select(a => a.DocumentId)
                    .ToList();

                var taskDocs = documents
                    .Where(d => taskDocIds.Contains(d.Id))
                    .ToList();

                response.ReportedPreSignedUrls = taskDocs
                    .Select(d => _s3Service.GeneratePreSignedUrl(d.S3Key))
                    .ToList();

                // Add team members
                var taskMemberEntries = teamMembers
                    .Where(m => m.TaskAllocationId == taskAllocation.Id)
                    .ToList();

                response.teamMembers = taskMemberEntries
                    .Select(m => m.Employee)
                    .Where(e => e != null)
                    .Select(e => e!.ToBasicEmployeeVMFromEmployee())
                    .ToList();

                // Add comments with attachments
                var commentVMs = new List<CommentVM>();
                var taskComments = allComments
                    .Where(c => c.TaskAllocationId == taskAllocation.Id)
                    .ToList();

                foreach (var comment in taskComments)
                {
                    var commentDocIds = attachments
                        .Where(a => a.ReferenceId == comment.Id)
                        .Select(a => a.DocumentId)
                        .ToList();

                    var commentDocs = documents
                        .Where(d => commentDocIds.Contains(d.Id))
                        .ToList();

                    var commentVm = comment.ToCommentVMFromTaskComment();
                    commentVm.PreSignedUrls = commentDocs
                        .Select(d => _s3Service.GeneratePreSignedUrl(d.S3Key))
                        .ToList();

                    commentVMs.Add(commentVm);
                }

                response.comments = commentVMs;

                // Checklists
                var activityId = taskAllocation.WorkItem?.ActivityId ?? Guid.Empty;

                var checkLists = await _context.ActivityCheckLists
                    .Where(c => c.ActivityId == activityId)
                    .ToListAsync();

                var checkListMappings = await _context.CheckListMappings
                    .Where(c => c.TaskAllocationId == taskAllocation.Id)
                    .ToListAsync();

                response.CheckList = checkLists.Select(check =>
                {
                    var isChecked = checkListMappings.Any(m => m.CheckListId == check.Id);
                    return check.ToCheckListVMFromActivityCheckList(check.ActivityId, isChecked);
                }).ToList();

                tasks.Add(response);
            }

            var VM = new
            {
                TotalCount = totalRecords,
                TotalPages = totalPages,
                CurrentPage = pageNumber,
                PageSize = pageSize,
                Data = tasks
            };

            _logger.LogInfo("Task list constructed successfully. Returning {Count} tasks.", tasks.Count);

            return Ok(ApiResponse<object>.SuccessResponse(VM, "Success", 200));
        }

        [HttpGet("get/{taskId}")]
        public async Task<IActionResult> GetTask(Guid taskId)
        {
            _logger.LogInfo("GetTask called with taskId: {TaskId}", taskId);

            // Validate input
            if (taskId == Guid.Empty)
            {
                _logger.LogWarning("Invalid taskId provided.");
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invalid data", 400));
            }

            // Fetch Task Allocation with required related data
            var taskAllocation = await _context.TaskAllocations
                .Include(t => t.Tenant)
                .Include(t => t.Employee)
                .Include(t => t.ReportedBy)
                .Include(t => t.ApprovedBy)
                .Include(t => t.WorkItem)
                .Include(t => t.WorkStatus)
                .FirstOrDefaultAsync(t => t.Id == taskId);

            if (taskAllocation == null)
            {
                _logger.LogWarning("Task not found for taskId: {TaskId}", taskId);
                return NotFound(ApiResponse<object>.ErrorResponse("Task Not Found", "Task not found", 404));
            }

            if (taskAllocation.Employee == null || taskAllocation.Tenant == null)
            {
                _logger.LogWarning("Task found but missing Employee or Tenant data for taskId: {TaskId}", taskId);
                return NotFound(ApiResponse<object>.ErrorResponse("Task Not Found", "Task not found", 404));
            }

            _logger.LogInfo("Task allocation found. Preparing response.");

            var taskVM = taskAllocation.TaskAllocationToTaskVM();

            // Fetch comments and attachments
            _logger.LogInfo("Fetching comments and attachments for taskId: {TaskId}", taskId);

            var comments = await _context.TaskComments
                .Where(c => c.TaskAllocationId == taskId)
                .Include(c => c.Employee)
                .ToListAsync();

            var commentIds = comments.Select(c => c.Id).ToList();

            var taskAttachments = await _context.TaskAttachments
                .Where(t => t.ReferenceId == taskId || commentIds.Contains(t.ReferenceId))
                .ToListAsync();

            var documentIds = taskAttachments.Select(t => t.DocumentId).Distinct().ToList();

            var documents = await _context.Documents
                .Where(d => documentIds.Contains(d.Id))
                .ToListAsync();

            // Fetch team members
            _logger.LogInfo("Fetching team members for taskId: {TaskId}", taskId);
            var team = await _context.TaskMembers
                .Where(m => m.TaskAllocationId == taskId)
                .Include(m => m.Employee)
                .ToListAsync();

            var teamMembers = team
                .Where(m => m.Employee != null)
                .Select(m => m.Employee!.ToBasicEmployeeVMFromEmployee())
                .ToList();

            taskVM.TeamMembers = teamMembers;

            // Attach documents to the main task
            _logger.LogInfo("Generating presigned URLs for task documents.");
            var taskDocumentIds = taskAttachments
                .Where(t => t.ReferenceId == taskId)
                .Select(t => t.DocumentId)
                .ToList();

            var taskDocuments = documents
                .Where(d => taskDocumentIds.Contains(d.Id))
                .ToList();

            taskVM.PreSignedUrls = taskDocuments
                .Select(d => _s3Service.GeneratePreSignedUrl(d.S3Key))
                .ToList();

            // Construct CommentVM list with document URLs
            _logger.LogInfo("Preparing comment response data.");
            var commentVMs = comments.Select(comment =>
            {
                var commentDocIds = taskAttachments
                    .Where(t => t.ReferenceId == comment.Id)
                    .Select(t => t.DocumentId)
                    .ToList();

                var commentDocs = documents
                    .Where(d => commentDocIds.Contains(d.Id))
                    .ToList();

                var commentVM = comment.ToCommentVMFromTaskComment();
                commentVM.PreSignedUrls = commentDocs
                    .Select(d => _s3Service.GeneratePreSignedUrl(d.S3Key))
                    .ToList();

                return commentVM;
            }).ToList();

            taskVM.Comments = commentVMs;

            _logger.LogInfo("Task details prepared successfully for taskId: {TaskId}", taskId);
            return Ok(ApiResponse<object>.SuccessResponse(taskVM, "Success", 200));
        }

        [HttpGet("filter/{projectId}")]
        public async Task<IActionResult> GetTaskFilterObject(Guid projectId)
        {
            // Get the current tenant from claims/context
            Guid tenantId = GetTenantId();

            // Log API invocation with the project and tenant for traceability
            _logger.LogInfo("Fetching filter objects for ProjectId={ProjectId}, TenantId={TenantId}", projectId, tenantId);

            try
            {
                // AsNoTracking for improved performance—no intention to update these records
                // Only fetch & project properties actually required (DTO projection)
                var tasks = await _context.TaskAllocations
                .Include(t => t.WorkItem)
                    .ThenInclude(wi => wi!.WorkArea)
                        .ThenInclude(wa => wa!.Floor)
                            .ThenInclude(f => f!.Building)
                .Include(t => t.WorkItem)
                    .ThenInclude(wi => wi!.ActivityMaster)
                        .ThenInclude(a => a!.ActivityGroup)
                            .ThenInclude(ag => ag!.Service)
                .Where(t => t.WorkItem != null &&
                    t.WorkItem.WorkArea != null &&
                    t.WorkItem.WorkArea.Floor != null &&
                    t.WorkItem.WorkArea.Floor.Building != null &&
                    t.WorkItem.WorkArea.Floor.Building.ProjectId == projectId &&
                    t.TenantId == tenantId).ToListAsync();

                // Distinct by Id (since projection doesn't guarantee uniqueness across different allocations)
                var buildings = tasks.Where(t => t.WorkItem != null && t.WorkItem.WorkArea != null && t.WorkItem.WorkArea.Floor != null && t.WorkItem.WorkArea.Floor.Building != null)
                .Select(t => t.WorkItem!.WorkArea!.Floor!.Building!)
                .Select(b => new
                {
                    Id = b.Id,
                    Name = b.Name
                }).Distinct().ToList();

                var floors = tasks.Where(t => t.WorkItem != null && t.WorkItem.WorkArea != null && t.WorkItem.WorkArea.Floor != null)
                    .Select(t => t.WorkItem!.WorkArea!.Floor!)
                    .Select(f => new
                    {
                        Id = f.Id,
                        Name = f.FloorName,
                        BuildingId = f.BuildingId
                    }).Distinct().ToList();

                var activities = tasks.Where(t => t.WorkItem != null &&
                    t.WorkItem.ActivityMaster != null &&
                    t.WorkItem.ActivityMaster.ActivityGroup != null &&
                    t.WorkItem.ActivityMaster.ActivityGroup.Service != null)
                        .Select(t => t.WorkItem!.ActivityMaster!)
                    .Select(a => new
                    {
                        Id = a.Id,
                        Name = a.ActivityName
                    }).Distinct().ToList();

                var services = tasks.Where(t => t.WorkItem != null &&
                    t.WorkItem.ActivityMaster != null &&
                    t.WorkItem.ActivityMaster.ActivityGroup != null &&
                    t.WorkItem.ActivityMaster.ActivityGroup.Service != null)
                    .Select(t => t.WorkItem!.ActivityMaster!.ActivityGroup!.Service!)
                    .Select(s => new
                    {
                        Id = s.Id,
                        Name = s.Name
                    }).Distinct().ToList();

                var response = new
                {
                    Buildings = buildings,
                    Floors = floors,
                    Activities = activities,
                    Services = services
                };

                _logger.LogInfo("Successfully fetched filter objects for ProjectId={ProjectId}, TenantId={TenantId}", projectId, tenantId);

                // Use DTO in API response for clarity and easier frontend usage
                return Ok(ApiResponse<object>.SuccessResponse(response, "Filter object for task fetched successfully", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to fetch filter objects for ProjectId={ProjectId}, TenantId={TenantId}", projectId, tenantId);
                // Return a standard error result
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Failed to fetch filter object.", 500));
            }
        }


        /// <summary>
        /// Approves a reported task after validation, updates status, and stores attachments/comments.
        /// </summary>
        /// <param name="approveTask">DTO containing task approval details.</param>
        /// <returns>IActionResult indicating success or failure.</returns>

        [HttpPost("approve")]
        public async Task<IActionResult> ApproveTask(ApproveTaskDto approveTask)
        {
            Guid tenantId = _userHelper.GetTenantId();
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            _logger.LogInfo("Employee {EmployeeId} is attempting to approve Task {TaskId}", loggedInEmployee.Id, approveTask.Id);

            // Fetch task allocation with work item, only if it's reported
            var taskAllocation = await _context.TaskAllocations
                .Include(t => t.WorkItem)
                .FirstOrDefaultAsync(t => t.Id == approveTask.Id && t.TenantId == tenantId && t.ReportedDate != null);

            if (taskAllocation == null)
            {
                _logger.LogWarning("Task {TaskId} not found or not reported yet for Tenant {TenantId} by Employee {EmployeeId}",
                    approveTask.Id, tenantId, loggedInEmployee.Id);

                return NotFound(ApiResponse<object>.ErrorResponse("Task not found", "Task not found", 404));
            }

            // Check for permission to approve tasks
            var hasPermission = await _permissionServices.HasPermission(PermissionsMaster.ApproveTask, loggedInEmployee.Id);
            if (!hasPermission)
            {
                _logger.LogWarning("Employee {EmployeeId} attempted to approve Task {TaskId} without permission", loggedInEmployee.Id, approveTask.Id);
                return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "User not authorized to approve tasks", 403));
            }

            // Validation: Approved task count cannot exceed completed task count
            if (taskAllocation.CompletedTask < approveTask.ApprovedTask)
            {
                _logger.LogWarning("Invalid approval attempt on Task {TaskId}: Approved tasks ({ApprovedTask}) > Completed tasks ({CompletedTask})",
                    approveTask.Id, approveTask.ApprovedTask, taskAllocation.CompletedTask);

                return BadRequest(ApiResponse<object>.ErrorResponse("Approved tasks cannot be greater than completed tasks",
                    "Approved tasks cannot be greater than completed tasks", 400));
            }

            // Update task allocation details
            taskAllocation.ApprovedById = loggedInEmployee.Id;
            taskAllocation.ApprovedDate = DateTime.UtcNow;
            taskAllocation.WorkStatusId = approveTask.WorkStatus;
            taskAllocation.ReportedTask = approveTask.ApprovedTask;

            // Add a comment (optional)
            var comment = new TaskComment
            {
                TaskAllocationId = taskAllocation.Id,
                CommentDate = DateTime.UtcNow,
                Comment = approveTask.Comment ?? string.Empty,
                CommentedBy = loggedInEmployee.Id,
                TenantId = tenantId
            };
            _context.TaskComments.Add(comment);

            var workAreaId = taskAllocation.WorkItem?.WorkAreaId;
            var workArea = await _context.WorkAreas.Include(a => a.Floor)
                .FirstOrDefaultAsync(a => a.Id == workAreaId) ?? new WorkArea();

            var buildingId = workArea.Floor?.BuildingId;

            var building = await _context.Buildings
                .FirstOrDefaultAsync(b => b.Id == buildingId);
            var projectId = building?.ProjectId;
            int numberofImages = 0;

            // Handle image attachments, if any
            if (approveTask.Images?.Count > 0)
            {
                var batchId = Guid.NewGuid();

                foreach (var image in approveTask.Images)
                {
                    if (string.IsNullOrEmpty(image.Base64Data))
                    {
                        _logger.LogWarning("Image for Task {TaskId} is missing base64 data", approveTask.Id);
                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
                    }

                    var base64 = image.Base64Data.Contains(",") ? image.Base64Data[(image.Base64Data.IndexOf(",") + 1)..] : image.Base64Data;

                    var fileType = _s3Service.GetContentTypeFromBase64(base64);
                    var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_comment");
                    var objectKey = $"tenant-{tenantId}/project-{projectId}/Activity/{fileName}";

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

                    var document = new Document
                    {
                        BatchId = batchId,
                        UploadedById = loggedInEmployee.Id,
                        FileName = fileName,
                        ContentType = image.ContentType ?? string.Empty,
                        S3Key = objectKey,
                        //Base64Data = image.Base64Data,
                        FileSize = image.FileSize,
                        UploadedAt = DateTime.UtcNow,
                        TenantId = tenantId
                    };

                    _context.Documents.Add(document);

                    var attachment = new TaskAttachment
                    {
                        DocumentId = document.Id,
                        ReferenceId = comment.Id
                    };

                    _context.TaskAttachments.Add(attachment);

                    _logger.LogInfo("Attachment uploaded for Task {TaskId}: {FileName}", approveTask.Id, fileName);
                    numberofImages += 1;
                }
            }

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

            var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Task_Report", NumberOfImages = numberofImages, ProjectId = projectId };
            await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);

            _logger.LogInfo("Task {TaskId} successfully approved by Employee {EmployeeId}", approveTask.Id, loggedInEmployee.Id);

            _ = 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}";

                await _firebase.SendApproveTaskMessageAsync(taskAllocation.Id, name, tenantId);

            });

            return Ok(ApiResponse<object>.SuccessResponse("Task has been approved", "Task has been approved", 200));
        }

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

            var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
            TaskFilter? expenseFilter = null;

            try
            {
                // First, try to deserialize directly. This is the expected case (e.g., from a web client).
                expenseFilter = JsonSerializer.Deserialize<TaskFilter>(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))
                    {
                        expenseFilter = JsonSerializer.Deserialize<TaskFilter>(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 expenseFilter;
        }
    }
}
