using AutoMapper;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Dtos.ServiceProject;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.ServiceProject;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Organization;
using Marco.Pms.Model.ViewModels.ServiceProject;
using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Service;
using Microsoft.EntityFrameworkCore;

namespace Marco.Pms.Services.Service
{
    public class ServiceProjectService : IServiceProject
    {

        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
        private readonly IServiceScopeFactory _serviceScopeFactory;
        private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
        private readonly ILoggingService _logger;
        private readonly CacheUpdateHelper _cache;
        private readonly IMapper _mapper;

        public ServiceProjectService(IDbContextFactory<ApplicationDbContext> dbContextFactory,
            IServiceScopeFactory serviceScopeFactory,
            ApplicationDbContext context,
            ILoggingService logger,
            CacheUpdateHelper cache,
            IMapper mapper)
        {
            _serviceScopeFactory = serviceScopeFactory;
            _context = context;
            _logger = logger;
            _cache = cache;
            _mapper = mapper;
            _dbContextFactory = dbContextFactory;
        }

        #region =================================================================== Service Project Functions ===================================================================
        public async Task<ApiResponse<object>> GetServiceProjectListAsync(int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId)
        {
            try
            {

                var serviceProjectQuery = _context.ServiceProjects
                    .Include(sp => sp.Client)
                    .Include(sp => sp.Status)
                    .Include(sp => sp.CreatedBy).ThenInclude(e => e!.JobRole)
                    .Where(sp => sp.TenantId == tenantId && sp.IsActive);

                var totalEntites = await serviceProjectQuery.CountAsync();
                var totalPages = (int)Math.Ceiling((double)totalEntites / pageSize);

                var serviceProjects = await serviceProjectQuery
                    .OrderByDescending(e => e.CreatedAt)
                    .Skip((pageNumber - 1) * pageSize)
                    .Take(pageSize)
                    .ToListAsync();

                var serviceProjectIds = serviceProjects.Select(sp => sp.Id).ToList();
                var serviceProjectServiceMappings = await _context.ServiceProjectServiceMapping
                    .Include(sps => sps.Service)
                    .Where(sps => serviceProjectIds.Contains(sps.ProjectId) &&
                    sps.Service != null &&
                    sps.TenantId == tenantId)
                    .ToListAsync();

                var serviceProjectVMs = serviceProjects.Select(sp =>
                {
                    var services = serviceProjectServiceMappings.Where(sps => sps.ProjectId == sp.Id).Select(sps => sps.Service!).ToList();
                    var result = _mapper.Map<ServiceProjectVM>(sp);
                    result.Services = _mapper.Map<List<ServiceMasterVM>>(services);
                    return result;
                }).ToList();

                var response = new
                {
                    CurrentPage = pageNumber,
                    TotalPages = totalPages,
                    TotalEntites = totalEntites,
                    Data = serviceProjectVMs,
                };

                _logger.LogInfo("Successfully retrieved a total of {ProjectCount} projects.", serviceProjectVMs.Count);
                return ApiResponse<object>.SuccessResponse(response, "Projects retrieved successfully.", 200);

            }
            catch (Exception ex)
            {
                // --- Step 5: Graceful Error Handling ---
                _logger.LogError(ex, "An unexpected error occurred in GetAllProjects for tenant {TenantId}.", tenantId);
                return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
            }

        }
        public async Task<ApiResponse<object>> GetServiceProjectDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId)
        {
            var serviceProject = await _context.ServiceProjects
                .Include(sp => sp.Client)
                .Include(sp => sp.Status)
                .Include(sp => sp.CreatedBy).ThenInclude(e => e!.JobRole)
                .Include(sp => sp.UpdatedBy).ThenInclude(e => e!.JobRole)
                .FirstOrDefaultAsync(sp => sp.Id == id && sp.TenantId == tenantId);
            if (serviceProject == null)
            {
                return ApiResponse<object>.ErrorResponse("Service Project not found", "Service Project not found", 404);
            }
            var services = await _context.ServiceProjectServiceMapping
                    .Include(sps => sps.Service)
                    .Where(sps => sps.ProjectId == serviceProject.Id &&
                        sps.Service != null &&
                        sps.TenantId == tenantId)
                    .Select(sps => sps.Service!)
                    .ToListAsync();
            //var numberOfJobs = await _context.JobTickets.Where(jt => jt.ProjectId == serviceProject.Id && jt.TenantId == tenantId).CountAsync();

            var response = _mapper.Map<ServiceProjectDetailsVM>(serviceProject);
            response.Services = _mapper.Map<List<ServiceMasterVM>>(services);
            //response.NumberOfJobs = numberOfJobs;
            response.NumberOfJobs = 0;
            return ApiResponse<object>.SuccessResponse(response, "Service Project Details fetched successfully", 200);
        }
        public async Task<ApiResponse<object>> CreateServiceProjectAsync(ServiceProjectDto model, Employee loggedInEmployee, Guid tenantId)
        {
            var serviceIds = model.Services.Where(s => s.IsActive).Select(s => s.ServiceId).ToList();
            var clientTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.ClientId && o.IsActive);
            });
            var serviceTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.ServiceMasters.Where(s => serviceIds.Contains(s.Id) && s.TenantId == tenantId && s.IsActive).ToListAsync();
            });
            var statusTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.StatusMasters.FirstOrDefaultAsync(s => s.Id == model.StatusId);
            });

            await Task.WhenAll(clientTask, serviceTask, statusTask);

            var client = clientTask.Result;
            var services = serviceTask.Result;
            var status = statusTask.Result;

            if (client == null)
            {
                return ApiResponse<object>.ErrorResponse("Client not found", "Client not found", 404);
            }
            if (status == null)
            {
                return ApiResponse<object>.ErrorResponse("Project Status not found", "Project Status not found", 404);
            }

            var serviceProject = _mapper.Map<ServiceProject>(model);
            serviceProject.Id = Guid.NewGuid();
            serviceProject.CreatedById = loggedInEmployee.Id;
            serviceProject.CreatedAt = DateTime.UtcNow;
            serviceProject.IsActive = true;
            serviceProject.TenantId = tenantId;

            var projectServiceMapping = model.Services.Where(sdto => services.Any(s => s.Id == sdto.ServiceId)).Select(sdto => new ServiceProjectServiceMapping
            {
                ServiceId = sdto.ServiceId,
                ProjectId = serviceProject.Id,
                TenantId = tenantId
            }).ToList();
            try
            {
                _context.ServiceProjects.Add(serviceProject);
                _context.ServiceProjectServiceMapping.AddRange(projectServiceMapping);

                await _context.SaveChangesAsync();

                _logger.LogInfo("Service Project {ProjectId} created successfully for TenantId={TenantId}, by Employee {EmployeeId}.",
                  serviceProject.Id, tenantId, loggedInEmployee);

                var serviceProjectVM = _mapper.Map<ServiceProjectVM>(serviceProject);

                serviceProjectVM.Client = _mapper.Map<BasicOrganizationVm>(client);
                serviceProjectVM.Status = status;

                serviceProjectVM.Services = services.Where(s => serviceIds.Contains(s.Id)).Select(s => _mapper.Map<ServiceMasterVM>(s)).ToList();

                serviceProjectVM.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
                return ApiResponse<object>.SuccessResponse(serviceProjectVM, "An Successfullly occurred while saving the project.", 201);

            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "DB Failure: Service Project creation failed for TenantId={TenantId}. Rolling back.", tenantId);
                return ApiResponse<object>.ErrorResponse("An error occurred while saving the project.", ex.Message, 500);
            }


        }
        public async Task<ApiResponse<object>> UpdateServiceProjectAsync(Guid id, ServiceProjectDto model, Employee loggedInEmployee, Guid tenantId)
        {
            try
            {
                var serviceIds = model.Services.Select(s => s.ServiceId).ToList();
                var clientTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.ClientId && o.IsActive);
                });
                var serviceTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.ServiceMasters.Where(s => serviceIds.Contains(s.Id) && s.TenantId == tenantId && s.IsActive).ToListAsync();
                });
                var statusTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.StatusMasters.FirstOrDefaultAsync(s => s.Id == model.StatusId);
                });

                await Task.WhenAll(clientTask, serviceTask, statusTask);

                var client = clientTask.Result;
                var services = serviceTask.Result;
                var status = statusTask.Result;

                if (client == null)
                {
                    return ApiResponse<object>.ErrorResponse("Client not found", "Client not found", 404);
                }
                if (status == null)
                {
                    return ApiResponse<object>.ErrorResponse("Project Status not found", "Project Status not found", 404);
                }

                var serviceProject = await _context.ServiceProjects.Where(sp => sp.Id == id && sp.TenantId == tenantId && sp.IsActive).FirstOrDefaultAsync();

                if (serviceProject == null)
                {
                    _logger.LogWarning("Attempt to update non-existent Service project with ID {ProjectId} by user {UserId}.", id, loggedInEmployee.Id);
                    return ApiResponse<object>.ErrorResponse("Project not found.", $"No project found with ID {id}.", 404);
                }

                _mapper.Map(model, serviceProject);

                serviceProject.UpdatedAt = DateTime.UtcNow;
                serviceProject.UpdatedById = loggedInEmployee.Id;

                var serviceProjectServiceMappings = await _context.ServiceProjectServiceMapping
                    .AsNoTracking()
                    .Where(sps => sps.ProjectId == serviceProject.Id && sps.TenantId == tenantId)
                    .ToListAsync();

                var newMapping = new List<ServiceProjectServiceMapping>();
                var removedMapping = new List<ServiceProjectServiceMapping>();

                foreach (var dto in model.Services)
                {
                    var serviceProjectServiceMapping = serviceProjectServiceMappings
                        .FirstOrDefault(sps => sps.ServiceId == dto.ServiceId);

                    if (dto.IsActive && serviceProjectServiceMapping == null)
                    {
                        newMapping.Add(new ServiceProjectServiceMapping
                        {
                            Id = Guid.NewGuid(),
                            ServiceId = dto.ServiceId,
                            ProjectId = serviceProject.Id,
                            TenantId = tenantId,
                        });
                    }
                    else if (!dto.IsActive && serviceProjectServiceMapping != null)
                    {
                        removedMapping.Add(serviceProjectServiceMapping);
                    }
                }

                _context.ServiceProjectServiceMapping.AddRange(newMapping);
                _context.ServiceProjectServiceMapping.RemoveRange(removedMapping);

                await _context.SaveChangesAsync();

                var serviceProjectTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.ServiceProjects
                        .Include(sp => sp.Client)
                        .Include(sp => sp.Status)
                        .Include(sp => sp.CreatedBy).ThenInclude(e => e!.JobRole)
                        .Where(sp => sp.TenantId == tenantId && sp.IsActive).FirstOrDefaultAsync();
                });

                var servicesTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.ServiceProjectServiceMapping
                        .Include(sps => sps.Service)
                        .Where(sps => sps.ProjectId == serviceProject.Id && sps.Service != null && sps.TenantId == tenantId)
                        .Select(sps => sps.Service!)
                        .ToListAsync();
                });

                await Task.WhenAll(serviceProjectTask, servicesTask);


                serviceProject = serviceProjectTask.Result;
                services = servicesTask.Result;

                ServiceProjectVM serviceProjectVm = _mapper.Map<ServiceProjectVM>(serviceProject);
                serviceProjectVm.Services = _mapper.Map<List<ServiceMasterVM>>(services);

                return ApiResponse<object>.SuccessResponse(serviceProjectVm, "Service Project updated successfully.", 200);

            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An unexpected error occurred in Updating Service Project for tenant {TenantId}.", tenantId);
                return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
            }

        }
        public async Task<ApiResponse<object>> DeActivateServiceProjectAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId)
        {
            try
            {

                var serviceProject = await _context.ServiceProjects
                    .Where(sp => sp.Id == id && sp.TenantId == tenantId).FirstOrDefaultAsync();

                if (serviceProject == null)
                {
                    _logger.LogWarning("Attempt to de-activate non-existent Service project with ID {ProjectId} by user {UserId}.", id, loggedInEmployee.Id);
                    return ApiResponse<object>.ErrorResponse("Project not found.", $"No project found with ID {id}.", 404);
                }

                serviceProject.IsActive = isActive;

                await _context.SaveChangesAsync();

                _logger.LogInfo("Successfully de-activated service project {ProjectId}", id);
                return ApiResponse<object>.SuccessResponse(new { }, "Projects de-activated successfully.", 200);

            }
            catch (Exception ex)
            {
                // --- Step 5: Graceful Error Handling ---
                _logger.LogError(ex, "An unexpected error occurred in DeActivateServiceProject for tenant {TenantId}.", tenantId);
                return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
            }
        }

        #endregion

        #region =================================================================== Expense Functions ===================================================================
        #endregion

        #region =================================================================== Expense Functions ===================================================================
        #endregion
    }
}
