using Marco.Pms.DataAccess.Data;
using Marco.Pms.Helpers.Utility;
using Marco.Pms.Model.MongoDBModels.Masters;
using Marco.Pms.Model.MongoDBModels.Project;
using MarcoBMS.Services.Service;
using Microsoft.EntityFrameworkCore;

namespace Marco.Pms.Services.Helpers
{
    public class GeneralHelper
    {
        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
        private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
        private readonly ILoggingService _logger;
        private readonly FeatureDetailsHelper _featureDetailsHelper;
        private static readonly Guid SuperTenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
        public GeneralHelper(IDbContextFactory<ApplicationDbContext> dbContextFactory,
            ApplicationDbContext context,
            ILoggingService logger,
            FeatureDetailsHelper featureDetailsHelper)
        {
            _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _featureDetailsHelper = featureDetailsHelper ?? throw new ArgumentNullException(nameof(featureDetailsHelper));
        }
        public async Task<List<BuildingMongoDB>> GetProjectInfraFromDB(Guid projectId)
        {
            // Each task uses its own DbContext instance for thread safety. Projections are used for efficiency.

            // Task to fetch Buildings, Floors, and WorkAreas using projections
            var hierarchyTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                var buildings = await context.Buildings.AsNoTracking().Where(b => b.ProjectId == projectId).Select(b => new { b.Id, b.Name, b.Description }).ToListAsync();
                var buildingIds = buildings.Select(b => b.Id).ToList();
                var floors = await context.Floor.AsNoTracking().Where(f => buildingIds.Contains(f.BuildingId)).Select(f => new { f.Id, f.BuildingId, f.FloorName }).ToListAsync();
                var floorIds = floors.Select(f => f.Id).ToList();
                var workAreas = await context.WorkAreas.AsNoTracking().Where(wa => floorIds.Contains(wa.FloorId)).Select(wa => new { wa.Id, wa.FloorId, wa.AreaName }).ToListAsync();
                return (buildings, floors, workAreas);
            });

            // Task to get work summaries, AGGREGATED ON THE DATABASE SERVER
            var workSummaryTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                // This is the most powerful optimization. It avoids pulling all WorkItem rows.
                return await context.WorkItems.AsNoTracking()
                    .Where(wi => wi.WorkArea != null && wi.WorkArea.Floor != null && wi.WorkArea.Floor.Building != null && wi.WorkArea.Floor.Building.ProjectId == projectId)
                    .GroupBy(wi => wi.WorkAreaId) // Group by the parent WorkArea
                    .Select(g => new
                    {
                        WorkAreaId = g.Key,
                        PlannedWork = g.Sum(i => i.PlannedWork),
                        CompletedWork = g.Sum(i => i.CompletedWork)
                    })
                    .ToDictionaryAsync(x => x.WorkAreaId); // Return a ready-to-use dictionary for fast lookups
            });

            await Task.WhenAll(hierarchyTask, workSummaryTask);

            var (buildings, floors, workAreas) = await hierarchyTask;
            var workSummariesByWorkAreaId = await workSummaryTask;

            // --- Step 4: Build the hierarchy efficiently using Lookups ---
            // Using lookups is much faster (O(1)) than repeated .Where() calls (O(n)).
            var floorsByBuildingId = floors.ToLookup(f => f.BuildingId);
            var workAreasByFloorId = workAreas.ToLookup(wa => wa.FloorId);

            var buildingMongoList = new List<BuildingMongoDB>();
            foreach (var building in buildings)
            {
                double buildingPlanned = 0, buildingCompleted = 0;
                var floorMongoList = new List<FloorMongoDB>();

                foreach (var floor in floorsByBuildingId[building.Id]) // Fast lookup
                {
                    double floorPlanned = 0, floorCompleted = 0;
                    var workAreaMongoList = new List<WorkAreaMongoDB>();

                    foreach (var workArea in workAreasByFloorId[floor.Id]) // Fast lookup
                    {
                        // Get the pre-calculated summary from the dictionary. O(1) operation.
                        workSummariesByWorkAreaId.TryGetValue(workArea.Id, out var summary);
                        var waPlanned = summary?.PlannedWork ?? 0;
                        var waCompleted = summary?.CompletedWork ?? 0;

                        workAreaMongoList.Add(new WorkAreaMongoDB
                        {
                            Id = workArea.Id.ToString(),
                            AreaName = workArea.AreaName,
                            PlannedWork = waPlanned,
                            CompletedWork = waCompleted
                        });

                        floorPlanned += waPlanned;
                        floorCompleted += waCompleted;
                    }

                    floorMongoList.Add(new FloorMongoDB
                    {
                        Id = floor.Id.ToString(),
                        FloorName = floor.FloorName,
                        PlannedWork = floorPlanned,
                        CompletedWork = floorCompleted,
                        WorkAreas = workAreaMongoList
                    });

                    buildingPlanned += floorPlanned;
                    buildingCompleted += floorCompleted;
                }

                buildingMongoList.Add(new BuildingMongoDB
                {
                    Id = building.Id.ToString(),
                    BuildingName = building.Name,
                    Description = building.Description,
                    PlannedWork = buildingPlanned,
                    CompletedWork = buildingCompleted,
                    Floors = floorMongoList
                });
            }
            return buildingMongoList;
        }

        /// <summary>
        /// Retrieves a list of work items for a specific work area, including a summary of tasks assigned for the current day.
        /// This method is highly optimized to run database operations in parallel and perform aggregations on the server.
        /// </summary>
        /// <param name="workAreaId">The ID of the work area.</param>
        /// <returns>A list of WorkItemMongoDB objects with calculated daily assignments.</returns>
        public async Task<List<WorkItemMongoDB>> GetWorkItemsListFromDB(Guid workAreaId)
        {
            _logger.LogInfo("Fetching DB work items for WorkAreaId: {WorkAreaId}", workAreaId);

            try
            {
                // --- Step 1: Run independent database queries in PARALLEL ---
                // We can fetch the WorkItems and the aggregated TaskAllocations at the same time.

                // Task 1: Fetch the WorkItem entities and their related data.
                var workItemsTask = _context.WorkItems
                    .Include(wi => wi.ActivityMaster)
                        .ThenInclude(am => am!.ActivityGroup)
                            .ThenInclude(ag => ag!.Service)
                    .Include(wi => wi.WorkCategoryMaster)
                    .Where(wi => wi.WorkAreaId == workAreaId && wi.ActivityMaster != null && wi.ActivityMaster.ActivityGroup != null
                    && wi.ActivityMaster.ActivityGroup.Service != null && wi.WorkCategoryMaster != null)
                    .AsNoTracking()
                    .ToListAsync();

                // Task 2: Fetch and AGGREGATE today's task allocations ON THE DATABASE SERVER.
                var todaysAssignmentsTask = Task.Run(async () =>
                {
                    // Correctly define "today's" date range to avoid precision issues.
                    var today = DateTime.UtcNow.Date;
                    var tomorrow = today.AddDays(1);

                    using var context = _dbContextFactory.CreateDbContext(); // Use a factory for thread safety

                    // This is the most powerful optimization:
                    // 1. It filters by WorkAreaId directly, making it independent of the first query.
                    // 2. It filters by a correct date range.
                    // 3. It groups and sums on the DB server, returning only a small summary.
                    return await context.TaskAllocations
                        .Where(t => t.WorkItem != null && t.WorkItem.WorkAreaId == workAreaId &&
                                     t.AssignmentDate >= today && t.AssignmentDate < tomorrow)
                        .GroupBy(t => t.WorkItemId)
                        .Select(g => new
                        {
                            WorkItemId = g.Key,
                            TodaysAssigned = g.Sum(x => x.PlannedTask)
                        })
                        // Return a dictionary for instant O(1) lookups later.
                        .ToDictionaryAsync(x => x.WorkItemId, x => x.TodaysAssigned);
                });

                // Await both parallel database operations to complete.
                await Task.WhenAll(workItemsTask, todaysAssignmentsTask);

                // Retrieve the results from the completed tasks.
                var workItemsFromDb = await workItemsTask;
                var todaysAssignments = await todaysAssignmentsTask;

                // --- Step 2: Map to the ViewModel/MongoDB model efficiently ---
                var workItemVMs = workItemsFromDb.Select(wi => new WorkItemMongoDB
                {
                    Id = wi.Id.ToString(),
                    WorkAreaId = wi.WorkAreaId.ToString(),
                    ParentTaskId = wi.ParentTaskId.ToString(),
                    ActivityMaster = wi.ActivityMaster != null ? new ActivityMasterMongoDB
                    {
                        Id = wi.ActivityMaster.Id.ToString(),
                        ActivityName = wi.ActivityMaster.ActivityName,
                        UnitOfMeasurement = wi.ActivityMaster.UnitOfMeasurement,
                        ActivityGroupMaster = new ActivityGroupMasterMongoDB
                        {
                            Id = wi.ActivityMaster.ActivityGroup!.Id.ToString(),
                            Name = wi.ActivityMaster.ActivityGroup.Name,
                            Description = wi.ActivityMaster.ActivityGroup.Description,
                            Service = new ServiceMasterMongoDB
                            {
                                Id = wi.ActivityMaster.ActivityGroup.Service!.Id.ToString(),
                                Name = wi.ActivityMaster.ActivityGroup.Service.Name,
                                Description = wi.ActivityMaster.ActivityGroup.Service.Description
                            }
                        }
                    } : null,
                    WorkCategoryMaster = wi.WorkCategoryMaster != null ? new WorkCategoryMasterMongoDB
                    {
                        Id = wi.WorkCategoryMaster.Id.ToString(),
                        Name = wi.WorkCategoryMaster.Name,
                        Description = wi.WorkCategoryMaster.Description
                    } : null,
                    PlannedWork = wi.PlannedWork,
                    CompletedWork = wi.CompletedWork,
                    Description = wi.Description,
                    TaskDate = wi.TaskDate,
                    // Use the fast dictionary lookup instead of the slow in-memory Where/Sum.
                    TodaysAssigned = todaysAssignments.GetValueOrDefault(wi.Id, 0)
                }).ToList();

                _logger.LogInfo("Successfully processed {WorkItemCount} work items for WorkAreaId: {WorkAreaId}", workItemVMs.Count, workAreaId);

                return workItemVMs;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred while fetching DB work items for WorkAreaId: {WorkAreaId}", workAreaId);
                // Return an empty list or re-throw, depending on your application's error handling strategy.
                return new List<WorkItemMongoDB>();
            }
        }

        /// <summary>
        /// Retrieves all enabled feature IDs for a given tenant based on their active subscription.
        /// </summary>
        /// <param name="tenantId">The unique identifier of the tenant.</param>
        /// <returns>A list of feature IDs available for the tenant.</returns>
        public async Task<List<Guid>> GetFeatureIdsByTenentIdAsync(Guid tenantId)
        {
            try
            {
                _logger.LogInfo("Fetching feature IDs for tenant: {TenantId}", tenantId);

                // Step 1: Get active tenant subscription with plan
                var tenantSubscription = await _context.TenantSubscriptions
                    .Include(ts => ts.Plan)
                    .AsNoTracking() // Optimization: Read-only query, no need to track
                    .FirstOrDefaultAsync(ts =>
                        ts.TenantId == tenantId &&
                        ts.Plan != null &&
                        !ts.IsCancelled &&
                        ts.EndDate.Date >= DateTime.UtcNow.Date); // FIX: Subscription should not be expired

                if (tenantSubscription == null)
                {
                    _logger.LogWarning("No active subscription found for tenant: {TenantId}", tenantId);
                    return new List<Guid>();
                }

                _logger.LogDebug("Active subscription found for tenant: {TenantId}, PlanId: {PlanId}",
                    tenantId, tenantSubscription.Plan!.Id);

                var featureIds = new List<Guid> { new Guid("2f3509b7-160d-410a-b9b6-daadd96c986d"), new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be") };

                // Step 2: Get feature details from Plan
                var featureDetails = await _featureDetailsHelper.GetFeatureDetails(tenantSubscription.Plan!.FeaturesId);

                if (featureDetails == null)
                {
                    _logger.LogWarning("No feature details found for tenant: {TenantId}, PlanId: {PlanId}",
                        tenantId, tenantSubscription.Plan!.Id);
                    return new List<Guid>();
                }

                // Step 3: Collect all enabled feature IDs from modules

                if (featureDetails.Modules?.Attendance?.Enabled == true)
                {
                    featureIds.AddRange(featureDetails.Modules.Attendance.FeatureId);
                    _logger.LogDebug("Added Attendance module features for tenant: {TenantId}", tenantId);
                }

                if (featureDetails.Modules?.ProjectManagement?.Enabled == true)
                {
                    featureIds.AddRange(featureDetails.Modules.ProjectManagement.FeatureId);
                    _logger.LogDebug("Added Project Management module features for tenant: {TenantId}", tenantId);
                }

                if (featureDetails.Modules?.Directory?.Enabled == true)
                {
                    featureIds.AddRange(featureDetails.Modules.Directory.FeatureId);
                    _logger.LogDebug("Added Directory module features for tenant: {TenantId}", tenantId);
                }

                if (featureDetails.Modules?.Expense?.Enabled == true)
                {
                    featureIds.AddRange(featureDetails.Modules.Expense.FeatureId);
                    _logger.LogDebug("Added Expense module features for tenant: {TenantId}", tenantId);
                }

                _logger.LogInfo("Returning {Count} feature IDs for tenant: {TenantId}", featureIds.Count, tenantId);

                return featureIds.Distinct().ToList();
            }
            catch (Exception ex)
            {
                // Step 4: Handle unexpected errors
                _logger.LogError(ex, "Error retrieving feature IDs for tenant: {TenantId}", tenantId);
                return new List<Guid>();
            }
        }

        /// <summary>
        /// Checks whether the tenant still has available seats (MaxUsers not exceeded).
        /// </summary>
        /// <param name="tenantId">The ID of the tenant to check.</param>
        /// <returns>True if seats are available; otherwise false.</returns>
        public async Task<bool> CheckSeatsRemainingAsync(Guid tenantId)
        {
            _logger.LogInfo("Checking seats remaining for TenantId: {TenantId}", tenantId);

            try
            {
                if (tenantId == SuperTenantId)
                {
                    return true;
                }
                // Run both queries concurrently
                var totalSeatsTask = GetMaxSeatsAsync(tenantId);
                var totalSeatsTakenTask = GetActiveEmployeesCountAsync(tenantId);

                await Task.WhenAll(totalSeatsTask, totalSeatsTakenTask);

                var totalSeats = await totalSeatsTask;
                var totalSeatsTaken = await totalSeatsTakenTask;

                _logger.LogInfo(
                    "TenantId: {TenantId} | TotalSeats: {TotalSeats} | SeatsTaken: {SeatsTaken}",
                    tenantId, totalSeats, totalSeatsTaken);

                bool seatsAvailable = totalSeats >= totalSeatsTaken;

                _logger.LogDebug("TenantId: {TenantId} | Seats Available: {SeatsAvailable}",
                    tenantId, seatsAvailable);

                return seatsAvailable;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error checking seats for TenantId: {TenantId}", tenantId);
                throw;
            }
        }

        /// <summary>
        /// Retrieves the maximum number of allowed seats (MaxUsers) for a tenant.
        /// </summary>
        private async Task<double> GetMaxSeatsAsync(Guid tenantId)
        {
            _logger.LogDebug("Fetching maximum seats for TenantId: {TenantId}", tenantId);

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

            var maxSeats = await dbContext.TenantSubscriptions
                .Where(ts => ts.TenantId == tenantId && !ts.IsCancelled)
                .Select(ts => ts.MaxUsers)
                .FirstOrDefaultAsync();

            _logger.LogDebug("TenantId: {TenantId} | MaxSeats: {MaxSeats}", tenantId, maxSeats);

            return maxSeats;
        }

        /// <summary>
        /// Counts the number of active employees for a tenant.
        /// </summary>
        private async Task<int> GetActiveEmployeesCountAsync(Guid tenantId)
        {
            _logger.LogDebug("Counting active employees for TenantId: {TenantId}", tenantId);

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

            var activeEmployees = await dbContext.Employees
                .Where(e => e.TenantId == tenantId && e.IsActive)
                .CountAsync();

            _logger.LogDebug("TenantId: {TenantId} | ActiveEmployees: {ActiveEmployees}", tenantId, activeEmployees);

            return activeEmployees;
        }

    }
}
