using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using MTWorkHR.Application.Identity;
using MTWorkHR.Application.Mapper;
using MTWorkHR.Application.Models;
using MTWorkHR.Core.Global;
using MTWorkHR.Core.IRepositories;
using MTWorkHR.Core.UnitOfWork;
using MTWorkHR.Infrastructure.Entities;
using MTWorkHR.Application.Services.Interfaces;
using MTWorkHR.Core.Email;
using MTWorkHR.Core.Entities;
using MTWorkHR.Infrastructure.UnitOfWorks;
using System.Linq.Dynamic.Core;
using Microsoft.AspNetCore.Http;
using MTWorkHR.Core.Entities.User;
using MTWorkHR.Core.Entities.Base;
using System.Linq;

namespace MTWorkHR.Application.Services
{
    public class OrderRequestService : BaseService<OrderRequest, OrderRequestDto, OrderRequestDto>, IOrderRequestService
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly IMailSender _emailSender;
        private readonly ApplicationUserManager _userManager;
        private readonly GlobalInfo _globalInfo;
        private readonly IUserService _userService;

        public OrderRequestService(IUnitOfWork unitOfWork, IMailSender emailSender, ApplicationUserManager userManager, GlobalInfo globalInfo, IUserService userService) : base(unitOfWork)
        {
            _unitOfWork = unitOfWork;
            _emailSender = emailSender;
            _userManager = userManager;
            _globalInfo = globalInfo;
            _userService = userService;
        }


        public override async Task<OrderRequestDto> GetById(long id)
        {
            var entity = await _unitOfWork.OrderRequest.GetByIdWithAllChildren(id);
            if(entity == null)
            {
                throw new AppException(ExceptionEnum.RecordNotExist);
            }
            var response = MapperObject.Mapper.Map<OrderRequestDto>(entity);
            response.Employee = await GetEmployeeDto(entity.RequestingEmployeeId);
            return response;
        }

        public async Task<OverTimeDto> GetOverTime(long id)
        {
            var entity = await _unitOfWork.OrderRequest.GetByIdWithAllChildren(id);
            if (entity == null)
            {
                throw new AppException(ExceptionEnum.RecordNotExist);
            }
            var response = MapperObject.Mapper.Map<OverTimeDto>(entity);
            response.Employee = await GetEmployeeDto(entity.RequestingEmployeeId);
            return response;
        }

        public async Task<BusinessTripDto> GetBusinessTrip(long id)
        {
            var entity = await _unitOfWork.OrderRequest.GetByIdWithAllChildren(id);
            if (entity == null)
            {
                throw new AppException(ExceptionEnum.RecordNotExist);
            }
            var response = MapperObject.Mapper.Map<BusinessTripDto>(entity);
            response.Employee = await GetEmployeeDto(entity.RequestingEmployeeId);
            return response;
        }

        public async Task<ServiceCertificateRequestDto> GetServiceCertificate(long id)
        {
            var entity = await _unitOfWork.OrderRequest.GetByIdWithAllChildren(id);
            if (entity == null)
            {
                throw new AppException(ExceptionEnum.RecordNotExist);
            }
            var response = MapperObject.Mapper.Map<ServiceCertificateRequestDto>(entity);
            response.Employee = await GetEmployeeDto(entity.RequestingEmployeeId);
            return response;
        }


        private async Task<EmployeeDto> GetEmployeeDto(string employeeId)
        {
            var user = await _userService.GetUserById(employeeId);
            var image = await _userService.GetProfileImage(employeeId);

            return new EmployeeDto
            {
                Id = employeeId,
                FirstName = user.FirstName,
                LastName = user.LastName,
                Email = user.Email,
                ProfileImagePath = image
            };
        }
        public  async Task<PagingResultDto<OrderRequestDto>> GetAll(OrderPagingInputDto PagingInputDto)
        {
            var res = await _unitOfWork.OrderRequest.GetAllWithChildrenAsync();
            var query = res.Item1;
            if (_globalInfo.UserType != UserTypeEnum.Business)
            {
                query = query.Where(m => m.RequestingEmployeeId != null && m.RequestingEmployeeId == _globalInfo.UserId);

            }
            if (PagingInputDto.Filter != null)
            {
                var filter = PagingInputDto.Filter;
                query = query.Where(u => u.RequestComments!.Contains(filter) 
                || u.OrderType.NameEn!.Contains(filter)
                || u.OrderType.NameAr!.Contains(filter)
                || u.LeaveType!.NameEn!.Contains(filter)
                || u.LeaveType!.NameAr!.Contains(filter));
            }
            if (PagingInputDto.SearchDate != null)
            {
               // query = query.Where(u => u.StartDate.Date <= PagingInputDto.SearchDate.Value.Date && u.EndDate!.Value.Date >= PagingInputDto.SearchDate.Value.Date) ;
                query = query.Where(u => u.CreateDate.Date == PagingInputDto.SearchDate.Value.Date) ;
            }
            if (PagingInputDto.OrderTypeId != null)
            {
                query = query.Where(u => u.OrderTypeId == PagingInputDto.OrderTypeId);
            }
            if (PagingInputDto.LeaveTypeId != null)
            {
                query = query.Where(u => u.LeaveTypeId == PagingInputDto.LeaveTypeId);
            }
            if (PagingInputDto.StatusId != null)
            {
                query = query.Where(u => (long?)u.OrderStatus == PagingInputDto.StatusId);
            }
          



            // UserName filter (join with users table)
            if (!string.IsNullOrEmpty(PagingInputDto.UserName))
            {
                var filter = PagingInputDto.UserName.ToLower();
                query = query.Join(
                    _userManager.Users,
                    order => order.RequestingEmployeeId,
                    user => user.Id,
                    (order, user) => new { Order = order, User = user }
                )
                .Where(joined =>
                    (joined.User.FirstName + " " + joined.User.LastName).ToLower().Contains(filter)
                //  ||joined.User.Email.ToLower().Contains(filter)
                )
                .Select(joined => joined.Order);
            }
            var total = await query.CountAsync();
            var order = query.OrderBy(PagingInputDto.OrderByField + " " + PagingInputDto.OrderType);

            var page = order.Skip((PagingInputDto.PageNumber * PagingInputDto.PageSize) - PagingInputDto.PageSize).Take(PagingInputDto.PageSize);

            var list = await MapToOrderRequestDtos(page);

            var response = new PagingResultDto<OrderRequestDto>
            {
                Result = list,
                Total = total
            };

            return response;
        }


        private async Task<List<OrderRequestDto>> MapToOrderRequestDtos(IQueryable<OrderRequest> page)
        {
            var list = MapperObject.Mapper.Map<List<OrderRequestDto>>(await page.ToListAsync());

            // Batch load user data to improve performance
            var employeeIds = list.Where(x => x.RequestingEmployeeId != null)
                                .Select(x => x.RequestingEmployeeId)
                                .Distinct()
                                .ToList();

            if (employeeIds.Any())
            {
                var users = await _userService.GetUsersWithAttachmentsByIds(employeeIds);
                if (users != null)
                {
                    var userLookup = users.ToDictionary(u => u.Id, u => u);

                    foreach (var item in list)
                    {
                        if (item.RequestingEmployeeId != null && userLookup.TryGetValue(item.RequestingEmployeeId, out var user))
                        {
                            item.Employee = new EmployeeDto
                            {
                                Id = user.Id,
                                FirstName = user.FirstName,
                                LastName = user.LastName,
                                Email = user.Email,
                                Position = user.Position ?? user.JobTitleName,
                                EmployeeName = $"{user.FirstName} {user.LastName}",
                                ProfileImagePath = user.UserAttachments?.FirstOrDefault(a => a.AttachmentTypeId == 9)?.FilePath
                            };
                        }
                    }
                }
            }

            return list;
        }
        public override async Task<OrderRequestDto> Create(OrderRequestDto input)
        {
            var period = DateTime.UtcNow.Year;
            input.LeaveTypeId = (input.LeaveTypeId != null && input.LeaveTypeId > 0 ) ? input.LeaveTypeId : null;
            input.OrderStatus = (input.OrderStatus != null && input.OrderStatus > 0 ) ? input.OrderStatus : ApprovalStatusEnum.Pending;
            if (string.IsNullOrEmpty( input.RequestingEmployeeId))
            {
                input.RequestingEmployeeId = _globalInfo.UserId;
            }
            if (input is BusinessTripDto)
            {
                input.OrderTypeId = 3;
            }
            else if (input is OverTimeDto)
            {
                input.OrderTypeId = 2;
            }
            else if (input is ServiceCertificateRequestDto)
            {
                input.OrderTypeId = 5;
            }
            var contract = await _unitOfWork.Contract.GetLatestActiveContract(input.RequestingEmployeeId);
            if(contract!= null)
                input.ContractId = contract.Id;
            if (input.OrderTypeId == 1 &&  input.LeaveTypeId == 1) // Annual vacation in contract
            {
                if (contract != null)
                {
                    var allocation = await _unitOfWork.OrderAllocation.GetUserAllocations(input.RequestingEmployeeId, input.OrderTypeId, input.LeaveTypeId, contract.Id, period);

                    if (allocation is null)
                    {
                        throw new AppException(ExceptionEnum.NoVacationBalance, "You do not have any allocations for this leave type.");
                    }
                    else
                    {
                        int sub = (int)(input.EndDate.Date - input.StartDate.Date).TotalDays;
                        int daysRequested = input.TotalDays > 0 ? (int)input.TotalDays : sub;
                        if (daysRequested > allocation.NumberOfDays)
                        {
                            throw new AppException(ExceptionEnum.NoVacationBalance, "You do not have enough days for this request");
                        }
                    }
                }
                
            }
          
            var orderRequest = MapperObject.Mapper.Map<OrderRequest>(input);
            orderRequest = await _unitOfWork.OrderRequest.AddAsync(orderRequest);
            await _unitOfWork.CompleteAsync();

            try
            {
                var requestingEmployee = await _userManager.Users.FirstOrDefaultAsync(x => x.Id == input.RequestingEmployeeId);
                var sendMailResult = await _emailSender.SendEmail(new EmailMessage
                {
                    To = requestingEmployee.Email,
                    Body = $"Your leave request for {input.StartDate:D} to {input.EndDate:D} " +
                    $"has been submitted successfully.",
                    Subject = "Leave Request Submitted",
                    userId = input.RequestingEmployeeId
                });
                if (!sendMailResult)
                {
                    throw new AppException("User created, but could not send the email!");
                }
            }
            catch (Exception ex)
            {
                //// Log or handle error, but don't throw...
            }
            var res = await _unitOfWork.OrderRequest.GetByIdWithTypes(orderRequest.Id);
            
            var response = MapperObject.Mapper.Map<OrderRequestDto>(res);
            if (response.RequestingEmployeeId != null)
            {
                var user = await _userService.GetUserWithAttachmentById(response.RequestingEmployeeId);
                if (user != null)
                {
                    response.Employee = new EmployeeDto { Id = response.RequestingEmployeeId, FirstName = user.FirstName, LastName = user.LastName, Email = user.Email };
                    var attach = user.UserAttachments?.FirstOrDefault(a => a.AttachmentTypeId == 9);
                    response.Employee.ProfileImagePath = attach?.FilePath;
                    //if (attach != null)
                    //    using (var stream = new MemoryStream(attach?.Content))
                    //    {
                    //        var file = new FormFile(stream, 0, stream.Length, Path.GetFileNameWithoutExtension(attach.FileName), attach.FileName)
                    //        {
                    //            Headers = new HeaderDictionary(),
                    //            ContentType = attach?.ContentType,
                    //        };

                    //        System.Net.Mime.ContentDisposition cd = new System.Net.Mime.ContentDisposition
                    //        {
                    //            FileName = file.FileName
                    //        };
                    //        file.ContentDisposition = cd.ToString();
                    //        response.Employee.ProfileImage = file;
                    //    }
                }
            }
            return response;
        }


        public async Task<OrderRequestDto> ChangeStatus(long id, int statusId )
        {
            var orderRequest = await _unitOfWork.OrderRequest.GetByIdAsync(id);
            if (orderRequest == null)
            {
                throw new AppException(ExceptionEnum.RecordNotExist);
            }
            orderRequest.OrderStatus = (ApprovalStatusEnum)statusId;
            if (orderRequest.OrderStatus == ApprovalStatusEnum.Approved)
            {
                long? contractId = orderRequest.ContractId;
                if (contractId == null || contractId == 0)
                { 
                    var contract = await _unitOfWork.Contract.GetLatestActiveContract(orderRequest.RequestingEmployeeId);
                    contractId = contract.Id;
                }
                var allocation = await _unitOfWork.OrderAllocation.GetUserAllocations(orderRequest.RequestingEmployeeId, orderRequest.OrderTypeId, orderRequest.LeaveTypeId, contractId.Value, DateTime.UtcNow.Year);
                if (allocation != null)
                {
                    int daysRequested = !orderRequest.EndDate.HasValue ? 1 : (int)(orderRequest.EndDate.Value - orderRequest.StartDate).TotalDays;
                    allocation.NumberOfDays -= daysRequested;
                }
            }
            await _unitOfWork.CompleteAsync();
            var response = MapperObject.Mapper.Map<OrderRequestDto>(orderRequest);
            return response;
        }



    }
}
