using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Activities;
using Marco.Pms.Model.Dtos.DocumentManager;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Filters;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;

namespace Marco.Pms.Services.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class ImageController : ControllerBase
    {
        private readonly ApplicationDbContext _context;
        private readonly S3UploadService _s3Service;
        private readonly UserHelper _userHelper;
        private readonly ILoggingService _logger;
        private readonly PermissionServices _permission;
        private readonly Guid tenantId;
        public ImageController(ApplicationDbContext context, S3UploadService s3Service, UserHelper userHelper, ILoggingService logger, PermissionServices permission)
        {
            _context = context;
            _s3Service = s3Service;
            _userHelper = userHelper;
            _logger = logger;
            tenantId = userHelper.GetTenantId();
            _permission = permission;
        }

        [HttpGet("images/{projectId}")]

        public async Task<IActionResult> GetImageList(Guid projectId, [FromQuery] string? filter, [FromQuery] int? pageNumber = 1, [FromQuery] int? pageSize = 10)
        {
            _logger.LogInfo("[GetImageList] Called by Employee for ProjectId: {ProjectId}", projectId);

            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Step 1: Validate project existence
            var isProjectExist = await _context.Projects.AnyAsync(p => p.Id == projectId && p.TenantId == tenantId);
            if (!isProjectExist)
            {
                _logger.LogWarning("[GetImageList] ProjectId: {ProjectId} not found", projectId);
                return BadRequest(ApiResponse<object>.ErrorResponse("Project not found", "Project not found in database", 400));
            }

            // Step 2: Check project access permission
            var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId);
            if (!hasPermission)
            {
                _logger.LogWarning("[GetImageList] Access denied for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}", loggedInEmployee.Id, projectId);
                return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "You don't have access", 403));
            }

            // Step 3: Deserialize filter
            ImageFilter? imageFilter = null;
            if (!string.IsNullOrWhiteSpace(filter))
            {
                try
                {
                    var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
                    //string unescapedJsonString = JsonSerializer.Deserialize<string>(filter, options) ?? "";
                    //imageFilter = JsonSerializer.Deserialize<ImageFilter>(unescapedJsonString, options);
                    imageFilter = JsonSerializer.Deserialize<ImageFilter>(filter, options);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning("[GetImageList] Failed to parse filter: {Message}", ex.Message);
                }
            }

            // Step 4: Extract filter values
            var buildingIds = imageFilter?.BuildingIds;
            var floorIds = imageFilter?.FloorIds;
            var workAreaIds = imageFilter?.WorkAreaIds;
            var activityIds = imageFilter?.ActivityIds;
            var workCategoryIds = imageFilter?.WorkCategoryIds;
            var startDate = imageFilter?.StartDate;
            var endDate = imageFilter?.EndDate;
            var uploadedByIds = imageFilter?.UploadedByIds;

            // Step 5: Fetch building > floor > area > work item hierarchy
            List<Building>? buildings = null;
            List<Floor>? floors = null;
            List<WorkArea>? workAreas = null;

            if (buildingIds != null && buildingIds.Count > 0)
            {

                buildings = await _context.Buildings
                .Where(b => b.ProjectId == projectId && buildingIds.Contains(b.Id))
                .ToListAsync();
            }
            else
            {
                buildings = await _context.Buildings
                .Where(b => b.ProjectId == projectId)
                .ToListAsync();

                buildingIds = buildings.Select(b => b.Id).ToList();
            }

            if (floorIds != null && floorIds.Count > 0)
            {
                floors = await _context.Floor
                .Where(f => buildingIds.Contains(f.BuildingId) && floorIds.Contains(f.Id))
                .ToListAsync();
            }
            else
            {
                floors = await _context.Floor
                .Where(f => buildingIds.Contains(f.BuildingId))
                .ToListAsync();

                floorIds = floors.Select(f => f.Id).ToList();
            }
            if (workAreaIds != null && workAreaIds.Count > 0)
            {
                workAreas = await _context.WorkAreas
                .Where(wa => floorIds.Contains(wa.FloorId) && workAreaIds.Contains(wa.Id))
                .ToListAsync();
            }
            else
            {
                workAreas = await _context.WorkAreas
                .Where(wa => floorIds.Contains(wa.FloorId))
                .ToListAsync();

                workAreaIds = workAreas.Select(wa => wa.Id).ToList();
            }

            var workItemsQuery = _context.WorkItems.Include(w => w.ActivityMaster).Include(w => w.WorkCategoryMaster)
                .Where(wi => workAreaIds.Contains(wi.WorkAreaId));
            if (activityIds?.Any() == true) workItemsQuery = workItemsQuery.Where(wi => activityIds.Contains(wi.ActivityId));

            if (workCategoryIds?.Any() == true)
            {
                workItemsQuery = workItemsQuery.Where(wi => wi.WorkCategoryMaster != null && workCategoryIds.Contains(wi.WorkCategoryMaster.Id));
            }
            var workItems = await workItemsQuery.ToListAsync();
            var workItemIds = workItems.Select(wi => wi.Id).ToList();

            // Step 6: Fetch task allocations and comments
            var tasks = await _context.TaskAllocations.Include(t => t.ReportedBy)
                .Where(t => workItemIds.Contains(t.WorkItemId)).ToListAsync();
            var taskIds = tasks.Select(t => t.Id).ToList();

            var comments = await _context.TaskComments.Include(c => c.Employee)
                .Where(c => taskIds.Contains(c.TaskAllocationId)).ToListAsync();
            var commentIds = comments.Select(c => c.Id).ToList();

            var attachments = await _context.TaskAttachments
                .Where(ta => taskIds.Contains(ta.ReferenceId) || commentIds.Contains(ta.ReferenceId)).ToListAsync();

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

            // Step 7: Fetch and filter documents
            List<DocumentBatchDto> documents = new List<DocumentBatchDto>();
            var docQuery = _context.Documents.Include(d => d.UploadedBy)
                .Where(d => documentIds.Contains(d.Id) && d.TenantId == tenantId);
            if (startDate != null && endDate != null)
            {
                docQuery = docQuery.Where(d => d.UploadedAt.Date >= startDate.Value.Date && d.UploadedAt.Date <= endDate.Value.Date);
            }
            if (pageNumber != null && pageSize != null)
            {
                documents = await docQuery
                .GroupBy(d => d.BatchId)
                .OrderByDescending(g => g.Max(d => d.UploadedAt))
                .Skip((pageNumber.Value - 1) * pageSize.Value)
                .Take(pageSize.Value)
                .Select(g => new DocumentBatchDto
                {
                    BatchId = g.Key,
                    Documents = g.ToList()
                })
                .ToListAsync();
                Console.Write("Pagenation Success");
            }


            // Step 8: Build response
            var documentVM = documents.Select(d =>
            {
                var docIds = d.Documents?.Select(x => x.Id).ToList() ?? new List<Guid>();
                var refId = attachments.FirstOrDefault(ta => docIds.Contains(ta.DocumentId))?.ReferenceId;
                var task = tasks.FirstOrDefault(t => t.Id == refId);
                var comment = comments.FirstOrDefault(c => c.Id == refId);

                var source = task != null ? "Report" : comment != null ? "Comment" : "";
                var uploadedBy = task?.ReportedBy ?? comment?.Employee;

                if (comment != null)
                {
                    task = tasks.FirstOrDefault(t => t.Id == comment.TaskAllocationId);
                }
                if (task != null)
                {
                    comment = comments.OrderBy(c => c.CommentDate).FirstOrDefault(c => c.TaskAllocationId == task.Id);
                }

                var workItem = workItems.FirstOrDefault(w => w.Id == task?.WorkItemId);
                var workArea = workAreas.FirstOrDefault(wa => wa.Id == workItem?.WorkAreaId);
                var floor = floors.FirstOrDefault(f => f.Id == workArea?.FloorId);
                var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId);

                return new
                {

                    BatchId = d.BatchId,
                    Documents = d.Documents?.Select(x => new
                    {
                        Id = x.Id,
                        thumbnailUrl = x.ThumbS3Key != null ? _s3Service.GeneratePreSignedUrl(x.ThumbS3Key) : (x.S3Key != null ? _s3Service.GeneratePreSignedUrl(x.S3Key) : null),
                        Url = x.S3Key != null ? _s3Service.GeneratePreSignedUrl(x.S3Key) : null,
                        UploadedBy = x.UploadedBy?.ToBasicEmployeeVMFromEmployee() ?? uploadedBy?.ToBasicEmployeeVMFromEmployee(),
                        UploadedAt = x.UploadedAt,
                    }).ToList(),
                    Source = source,
                    ProjectId = projectId,
                    BuildingId = building?.Id,
                    BuildingName = building?.Name,
                    FloorIds = floor?.Id,
                    FloorName = floor?.FloorName,
                    WorkAreaId = workArea?.Id,
                    WorkAreaName = workArea?.AreaName,
                    TaskId = task?.Id,
                    ActivityId = workItem?.ActivityMaster?.Id,
                    ActivityName = workItem?.ActivityMaster?.ActivityName,
                    WorkCategoryId = workItem?.WorkCategoryMaster?.Id,
                    WorkCategoryName = workItem?.WorkCategoryMaster?.Name,
                    CommentId = comment?.Id,
                    Comment = comment?.Comment
                };
            }).ToList();

            if (uploadedByIds?.Any() == true)
            {
                documentVM = documentVM.Where(d => d.Documents != null && d.Documents.Any(x => uploadedByIds.Contains(x.UploadedBy?.Id ?? Guid.Empty))).ToList();
            }

            _logger.LogInfo("[GetImageList] Fetched {Count} documents for ProjectId: {ProjectId}", documentVM.Count, projectId);
            return Ok(ApiResponse<object>.SuccessResponse(documentVM, $"{documentVM.Count} image records fetched successfully", 200));
        }

        [HttpGet("batch/{batchId}")]
        public async Task<IActionResult> GetImagesByBatch(Guid batchId)
        {
            _logger.LogInfo("GetImagesByBatch called for BatchId: {BatchId}", batchId);

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

            // Step 2: Retrieve all documents in the batch
            var documents = await _context.Documents
                .Include(d => d.UploadedBy)
                .Where(d => d.BatchId == batchId)
                .ToListAsync();

            if (!documents.Any())
            {
                _logger.LogWarning("No documents found for BatchId: {BatchId}", batchId);
                return NotFound(ApiResponse<object>.ErrorResponse("No images found", "No images associated with this batch", 404));
            }

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

            // Step 3: Get task/comment reference IDs linked to these documents
            var referenceIds = await _context.TaskAttachments
                .Where(ta => documentIds.Contains(ta.DocumentId))
            .Select(ta => ta.ReferenceId)
                .Distinct()
                .ToListAsync();

            // Step 4: Try to identify the source of the attachment (task or comment)
            var task = await _context.TaskAllocations
                .Include(t => t.ReportedBy)
                .FirstOrDefaultAsync(t => referenceIds.Contains(t.Id));

            TaskComment? comment = null;
            WorkItem? workItem = null;
            Employee? uploadedBy = null;
            string source = "";

            if (task != null)
            {
                uploadedBy = task.ReportedBy;
                workItem = await _context.WorkItems
                .Include(wi => wi.ActivityMaster)
                .Include(wi => wi.WorkCategoryMaster)
                    .FirstOrDefaultAsync(wi => wi.Id == task.WorkItemId);
                source = "Report";
            }
            else
            {
                comment = await _context.TaskComments
                    .Include(tc => tc.TaskAllocation)
                    .Include(tc => tc.Employee)
                    .FirstOrDefaultAsync(tc => referenceIds.Contains(tc.Id));
                var workItemId = comment?.TaskAllocation?.WorkItemId;

                uploadedBy = comment?.Employee;
                workItem = await _context.WorkItems
                    .Include(wi => wi.ActivityMaster)
                    .Include(wi => wi.WorkCategoryMaster)
                    .FirstOrDefaultAsync(wi => wi.Id == workItemId);
                source = "Comment";
            }

            // Step 5: Traverse up to building level
            var workAreaId = workItem?.WorkAreaId;
            var workArea = await _context.WorkAreas
                .Include(wa => wa.Floor)
                .FirstOrDefaultAsync(wa => wa.Id == workAreaId);

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

            // Step 6: Construct the response
            var response = new
            {

                BatchId = batchId,
                Documents = documents?.Select(x => new
                {
                    Id = x.Id,
                    thumbnailUrl = x.ThumbS3Key != null ? _s3Service.GeneratePreSignedUrl(x.ThumbS3Key) : (x.S3Key != null ? _s3Service.GeneratePreSignedUrl(x.S3Key) : null),
                    Url = x.S3Key != null ? _s3Service.GeneratePreSignedUrl(x.S3Key) : null,
                    UploadedBy = x.UploadedBy?.ToBasicEmployeeVMFromEmployee() ?? uploadedBy?.ToBasicEmployeeVMFromEmployee(),
                    UploadedAt = x.UploadedAt,
                }).ToList(),
                Source = source,
                ProjectId = building?.ProjectId,
                BuildingId = building?.Id,
                BuildingName = building?.Name,
                FloorIds = workArea?.Floor?.Id,
                FloorName = workArea?.Floor?.FloorName,
                WorkAreaId = workArea?.Id,
                WorkAreaName = workArea?.AreaName,
                TaskId = task?.Id,
                ActivityId = workItem?.ActivityMaster?.Id,
                ActivityName = workItem?.ActivityMaster?.ActivityName,
                WorkCategoryId = workItem?.WorkCategoryMaster?.Id,
                WorkCategoryName = workItem?.WorkCategoryMaster?.Name,
                CommentId = comment?.Id,
                Comment = comment?.Comment
            };

            _logger.LogInfo("Fetched {Count} image(s) for BatchId: {BatchId}", response.Documents?.Count ?? 0, batchId);

            return Ok(ApiResponse<object>.SuccessResponse(response, "Images for provided batchId fetched successfully", 200));
        }

        [HttpGet("{documentId}")]
        public async Task<IActionResult> GetImage(Guid documentId)
        {
            // Log the start of the image fetch process
            _logger.LogInfo("GetImage called for DocumentId: {DocumentId}", documentId);

            // Step 1: Get the currently logged-in employee (for future use like permission checks or auditing)
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Step 2: Fetch the document from the database based on the provided ID
            var document = await _context.Documents.FirstOrDefaultAsync(d => d.Id == documentId);

            // Step 3: If document doesn't exist, return a 400 Bad Request response
            if (document == null)
            {
                _logger.LogWarning("Document not found for DocumentId: {DocumentId}", documentId);
                return BadRequest(ApiResponse<object>.ErrorResponse("Document not found", "Document not found", 400));
            }

            // Step 4: Generate pre-signed URLs for thumbnail and full image (if keys exist)
            string? thumbnailUrl = document.ThumbS3Key != null
                ? _s3Service.GeneratePreSignedUrl(document.ThumbS3Key)
                : null;

            string? imageUrl = document.S3Key != null
                ? _s3Service.GeneratePreSignedUrl(document.S3Key)
                : null;

            // Step 5: Prepare the response object
            var response = new
            {
                ThumbnailUrl = thumbnailUrl,
                ImageUrl = imageUrl
            };

            // Step 6: Log successful fetch and return the result
            _logger.LogInfo("Image fetched successfully for DocumentId: {DocumentId}", documentId);
            return Ok(ApiResponse<object>.SuccessResponse(response, "Image fetched successfully", 200));
        }
    }
}
