using AutoMapper;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.AttendanceModule;
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.AttendanceVM;
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.Globalization;
using Document = Marco.Pms.Model.DocumentManager.Document;

namespace MarcoBMS.Services.Controllers
{
    [Authorize]
    [ApiController]
    [Route("api/[controller]")]
    public class AttendanceController : ControllerBase
    {
        private readonly ApplicationDbContext _context;
        private readonly IServiceScopeFactory _serviceScopeFactory;
        private readonly UserHelper _userHelper;
        private readonly PermissionServices _permission;
        private readonly ILoggingService _logger;
        private readonly Guid tenantId;
        private readonly IMapper _mapper;

        public AttendanceController(
             ApplicationDbContext context,
             UserHelper userHelper,
             IServiceScopeFactory serviceScopeFactory,
             ILoggingService logger,
             PermissionServices permission,
             IMapper mapper)
        {
            _context = context;
            _serviceScopeFactory = serviceScopeFactory;
            _userHelper = userHelper;
            _logger = logger;
            _permission = permission;
            _mapper = mapper;
            tenantId = userHelper.GetTenantId();
        }

        [HttpGet("log/attendance/{attendanceid}")]
        public async Task<IActionResult> GetAttendanceLogById(Guid attendanceid)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _s3Service = scope.ServiceProvider.GetRequiredService<S3UploadService>();

            List<AttendanceLog> lstAttendance = await _context.AttendanceLogs
                .Include(a => a.Document)
                .Include(a => a.Employee)
                .Include(a => a.UpdatedByEmployee)
                .Where(c => c.AttendanceId == attendanceid && c.TenantId == tenantId)
                .ToListAsync();

            List<AttendanceLogVM> attendanceLogVMs = new List<AttendanceLogVM>();
            foreach (var attendanceLog in lstAttendance)
            {
                string objectKey = attendanceLog.Document != null ? attendanceLog.Document.S3Key : string.Empty;
                string preSignedUrl = string.IsNullOrEmpty(objectKey) ? string.Empty : _s3Service.GeneratePreSignedUrl(objectKey);
                attendanceLogVMs.Add(attendanceLog.ToAttendanceLogVMFromAttendanceLog(preSignedUrl, preSignedUrl));
            }
            _logger.LogInfo("{count} Attendance records fetched successfully", lstAttendance.Count);
            return Ok(ApiResponse<object>.SuccessResponse(attendanceLogVMs, System.String.Format("{0} Attendance records fetched successfully", lstAttendance.Count), 200));

        }

        [HttpGet("log/employee/{employeeId}")]
        public async Task<IActionResult> GetAttendanceLogByEmployeeId(Guid employeeId, [FromQuery] DateTime? dateFrom = null, [FromQuery] DateTime? dateTo = null)
        {
            if (employeeId == Guid.Empty)
            {
                _logger.LogWarning("The employee Id sent by user is empty");
                return BadRequest(ApiResponse<object>.ErrorResponse("Employee ID is required and must not be Empty.", "Employee ID is required and must not be empty.", 400));
            }

            Employee? employee = await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == employeeId && e.TenantId == tenantId);
            if (employee == null)
            {
                _logger.LogWarning("Employee {EmployeeId} not found", employeeId);
                return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee not found in database", 404));
            }

            if (!dateFrom.HasValue)
            {
                dateFrom = DateTime.UtcNow;
            }
            if (!dateTo.HasValue)
            {
                var days = 0 - 7;
                dateTo = dateFrom.Value.AddDays(days);
            }

            List<Attendance> attendances = await _context.Attendes
                .Include(a => a.RequestedBy)
                    .ThenInclude(e => e!.JobRole)
                .Include(a => a.RequestedBy)
                    .ThenInclude(e => e!.JobRole)
                .Where(c => c.EmployeeId == employeeId && c.TenantId == tenantId && c.AttendanceDate.Date >= dateFrom && c.AttendanceDate.Date <= dateTo).ToListAsync();

            var projectIds = attendances.Select(a => a.ProjectID).Distinct().ToList();

            var projects = await _context.Projects.Where(p => projectIds.Contains(p.Id) && p.TenantId == tenantId).ToListAsync();

            List<EmployeeAttendanceVM> results = new List<EmployeeAttendanceVM>();

            if (employee != null)
            {
                foreach (var attendance in attendances)
                {
                    EmployeeAttendanceVM result = new EmployeeAttendanceVM
                    {
                        Id = attendance.Id,
                        EmployeeId = employee.Id,
                        FirstName = employee.FirstName,
                        LastName = employee.LastName,
                        ProjectId = attendance.ProjectID,
                        ProjectName = projects.Where(p => p.Id == attendance.ProjectID).Select(p => p.Name).FirstOrDefault(),
                        CheckInTime = attendance.InTime,
                        CheckOutTime = attendance.OutTime,
                        JobRoleName = employee.JobRole != null ? employee.JobRole.Name : "",
                        Activity = attendance.Activity,
                        EmployeeAvatar = null,
                        RequestedAt = attendance.RequestedAt,
                        RequestedBy = _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy),
                        ApprovedAt = attendance.ApprovedAt,
                        Approver = _mapper.Map<BasicEmployeeVM>(attendance.Approver)
                    };
                    results.Add(result);
                }
            }

            _logger.LogInfo("{count} Attendance records fetched successfully", results.Count);
            return Ok(ApiResponse<object>.SuccessResponse(results, System.String.Format("{0} Attendance records fetched successfully", results.Count), 200));


        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="projectId">ProjectID</param>
        /// <param name="date">YYYY-MM-dd</param>
        /// <returns></returns>

        [HttpGet("project/log")]
        public async Task<IActionResult> EmployeeAttendanceByDateRange([FromQuery] Guid projectId, [FromQuery] Guid? organizationId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _projectServices = scope.ServiceProvider.GetRequiredService<IProjectServices>();

            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            var project = await _context.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId);
            if (project == null)
            {
                _logger.LogWarning("Project {ProjectId} not found in database", projectId);
                return NotFound(ApiResponse<object>.ErrorResponse("Project not found."));
            }

            var hasTeamAttendancePermission = await _permission.HasPermission(PermissionsMaster.TeamAttendance, loggedInEmployee.Id);
            var hasSelfAttendancePermission = await _permission.HasPermission(PermissionsMaster.SelfAttendance, loggedInEmployee.Id);
            var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId);

            if (!hasProjectPermission)
            {
                _logger.LogWarning("Employee {EmployeeId} tries to access attendance of project {ProjectId}, but don't have access", loggedInEmployee.Id, projectId);
                return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", "Unauthorized access", 404));
            }

            DateTime fromDate = new DateTime();
            DateTime toDate = new DateTime();

            if (dateFrom != null && DateTime.TryParse(dateFrom, out fromDate) == false)
            {
                _logger.LogWarning("User sent Invalid fromDate while featching attendance logs");
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Date", "Invalid Date", 400));
            }
            if (dateTo != null && DateTime.TryParse(dateTo, out toDate) == false)
            {
                _logger.LogWarning("User sent Invalid toDate while featching attendance logs");
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Date", "Invalid Date", 400));
            }

            if (projectId == Guid.Empty)
            {
                _logger.LogWarning("The project Id sent by user is less than or equal to zero");
                return BadRequest(ApiResponse<object>.ErrorResponse("Project ID is required and must be greater than zero.", "Project ID is required and must be greater than zero.", 400));
            }

            var result = new List<EmployeeAttendanceVM>();
            //Attendance? attendance = null;
            ProjectAllocation? teamMember = null;

            if (dateFrom == null) fromDate = DateTime.UtcNow.Date;
            if (dateTo == null && dateFrom != null) toDate = fromDate.AddDays(-1);

            if (hasTeamAttendancePermission)
            {
                List<Attendance> lstAttendance = await _context.Attendes
                    .Include(a => a.RequestedBy)
                        .ThenInclude(e => e!.JobRole)
                    .Include(a => a.Approver)
                        .ThenInclude(e => e!.JobRole)
                    .Where(c => c.ProjectID == projectId && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == tenantId)
                    .ToListAsync();


                List<ProjectAllocation> projectteam = await _projectServices.GetTeamByProject(tenantId, projectId, organizationId, true);
                var jobRole = await _context.JobRoles.ToListAsync();
                foreach (Attendance? attendance in lstAttendance)
                {
                    var result1 = new EmployeeAttendanceVM()
                    {
                        Id = attendance.Id,
                        CheckInTime = attendance.InTime,
                        CheckOutTime = attendance.OutTime,
                        Activity = attendance.Activity,
                        ApprovedAt = attendance.ApprovedAt,
                        Approver = _mapper.Map<BasicEmployeeVM>(attendance.Approver),
                        RequestedAt = attendance.RequestedAt,
                        RequestedBy = _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy)
                    };
                    teamMember = projectteam.Find(x => x.EmployeeId == attendance.EmployeeId);
                    if (teamMember != null)
                    {
                        result1.EmployeeAvatar = null;
                        result1.EmployeeId = teamMember.EmployeeId;
                        if (teamMember.Employee != null)
                        {
                            result1.FirstName = teamMember.Employee.FirstName;
                            result1.LastName = teamMember.Employee.LastName;
                            result1.JobRoleName = teamMember.Employee.JobRole != null ? teamMember.Employee.JobRole.Name : null;
                            result1.OrganizationName = teamMember.Employee.Organization?.Name;
                            result1.ProjectId = projectId;
                            result1.ProjectName = teamMember.Project?.Name;
                        }
                        else
                        {
                            result1.FirstName = null;
                            result1.LastName = null;
                            result1.JobRoleName = null;
                            result1.OrganizationName = null;
                        }

                        result.Add(result1);
                    }

                }
            }
            else if (hasSelfAttendancePermission)
            {
                List<Attendance> lstAttendances = await _context.Attendes
                    .Include(a => a.RequestedBy)
                        .ThenInclude(e => e!.JobRole)
                    .Include(a => a.Approver)
                        .ThenInclude(e => e!.JobRole)
                    .Where(c => c.ProjectID == projectId && c.EmployeeId == loggedInEmployee.Id && c.AttendanceDate.Date >= fromDate.Date &&
                        c.AttendanceDate.Date <= toDate.Date && c.TenantId == tenantId)
                    .ToListAsync();

                var projectAllocationQuery = _context.ProjectAllocations
                    .Include(pa => pa.Project)
                    .Include(pa => pa.Employee)
                        .ThenInclude(e => e!.Organization)
                    .Include(pa => pa.Employee)
                        .ThenInclude(e => e!.JobRole)
                    .Where(pa => pa.EmployeeId == loggedInEmployee.Id && pa.TenantId == tenantId && pa.IsActive &&
                        pa.ProjectId == projectId && pa.Project != null &&
                        pa.Employee != null && pa.Employee.Organization != null && pa.Employee.JobRole != null);

                if (organizationId.HasValue)
                {
                    projectAllocationQuery = projectAllocationQuery.Where(pa => pa.Employee != null && pa.Employee.OrganizationId == organizationId);
                }

                var projectAllocation = await projectAllocationQuery.FirstOrDefaultAsync();

                foreach (var attendance in lstAttendances)
                {
                    if (projectAllocation != null)
                    {
                        EmployeeAttendanceVM result1 = new EmployeeAttendanceVM
                        {
                            Id = attendance.Id,
                            EmployeeAvatar = null,
                            EmployeeId = projectAllocation.EmployeeId,
                            FirstName = projectAllocation.Employee?.FirstName,
                            LastName = projectAllocation.Employee?.LastName,
                            JobRoleName = projectAllocation.Employee?.JobRole?.Name,
                            OrganizationName = projectAllocation.Employee?.Organization?.Name,
                            ProjectId = attendance.ProjectID,
                            ProjectName = projectAllocation.Project?.Name,
                            CheckInTime = attendance.InTime,
                            CheckOutTime = attendance.OutTime,
                            Activity = attendance.Activity,
                            ApprovedAt = attendance.ApprovedAt,
                            Approver = _mapper.Map<BasicEmployeeVM>(attendance.Approver),
                            RequestedAt = attendance.RequestedAt,
                            RequestedBy = _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy)
                        };
                        result.Add(result1);
                    }
                }
            }
            _logger.LogInfo("{count} Attendance records fetched successfully", result.Count);
            return Ok(ApiResponse<object>.SuccessResponse(result, System.String.Format("{0} Attendance records fetched successfully", result.Count), 200));
        }

        /// <summary>
        /// Retrieves employee attendance records for a specified project and date.
        /// The result is filtered based on the logged-in employee's permissions (Team or Self).
        /// </summary>
        /// <param name="projectId">The ID of the project.</param>
        /// <param name="organizationId">Optional. Filters attendance for employees of a specific organization.</param>
        /// <param name="includeInactive">Optional. Includes inactive employees in the team list if true.</param>
        /// <param name="date">Optional. The date for which to fetch attendance, in "yyyy-MM-dd" format. Defaults to the current UTC date.</param>
        /// <returns>An IActionResult containing a list of employee attendance records or an error response.</returns>

        [HttpGet("project/team")]
        public async Task<IActionResult> EmployeeAttendanceByProjectAsync([FromQuery] Guid projectId, [FromQuery] Guid? organizationId, [FromQuery] bool includeInactive, [FromQuery] string? date = null)
        {
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // --- 1. Initial Validation and Permission Checks ---
            _logger.LogInfo("Fetching attendance for ProjectId: {ProjectId}, TenantId: {TenantId}", projectId, tenantId);

            // Validate date format
            if (!DateTime.TryParse(date, out var forDate))
            {
                forDate = DateTime.UtcNow.Date; // Default to today's date
            }

            // Check if the project exists and if the employee has access
            var project = await _context.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId);
            if (project == null)
            {
                _logger.LogWarning("Project {ProjectId} not found in database", projectId);
                return NotFound(ApiResponse<object>.ErrorResponse("Project not found."));
            }

            if (!await _permission.HasProjectPermission(loggedInEmployee, projectId))
            {
                _logger.LogWarning("Unauthorized access attempt by EmployeeId: {EmployeeId} for ProjectId: {ProjectId}", loggedInEmployee.Id, projectId);
                return Unauthorized(ApiResponse<object>.ErrorResponse("You do not have permission to access this project."));
            }

            // --- 2. Delegate to Specific Logic Based on Permissions ---
            try
            {
                var hasTeamAttendancePermission = await _permission.HasPermission(PermissionsMaster.TeamAttendance, loggedInEmployee.Id);
                List<EmployeeAttendanceVM> result;

                if (hasTeamAttendancePermission)
                {
                    _logger.LogInfo("EmployeeId: {EmployeeId} has Team Attendance permission. Fetching team attendance.", loggedInEmployee.Id);
                    result = await GetTeamAttendanceAsync(tenantId, projectId, organizationId, forDate, includeInactive);
                }
                else if (await _permission.HasPermission(PermissionsMaster.SelfAttendance, loggedInEmployee.Id))
                {
                    _logger.LogInfo("EmployeeId: {EmployeeId} has Self Attendance permission. Fetching self attendance.", loggedInEmployee.Id);
                    result = await GetSelfAttendanceAsync(tenantId, projectId, loggedInEmployee.Id, organizationId, forDate);
                }
                else
                {
                    _logger.LogWarning("Access denied for EmployeeId: {EmployeeId}. No valid attendance permission found.", loggedInEmployee.Id);
                    return StatusCode(403, ApiResponse<object>.ErrorResponse("You do not have permission to view attendance.", new { }, 403));
                }

                _logger.LogInfo("Successfully fetched {Count} attendance records for ProjectId: {ProjectId}", result.Count, projectId);
                return Ok(ApiResponse<object>.SuccessResponse(result, $"{result.Count} attendance records fetched successfully."));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred while fetching attendance for ProjectId: {ProjectId}", projectId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("An internal server error occurred."));
            }
        }

        [HttpGet("regularize")]
        public async Task<IActionResult> GetRequestRegularizeAttendance([FromQuery] Guid projectId, [FromQuery] Guid? organizationId, [FromQuery] bool IncludeInActive)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _projectServices = scope.ServiceProvider.GetRequiredService<IProjectServices>();

            Employee LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
            var result = new List<EmployeeAttendanceVM>();
            var hasProjectPermission = await _permission.HasProjectPermission(LoggedInEmployee, projectId);

            if (!hasProjectPermission)
            {
                _logger.LogWarning("Employee {EmployeeId} tries to access attendance of project {ProjectId}, but don't have access", LoggedInEmployee.Id, projectId);
                return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", "Unauthorized access", 404));
            }

            List<Attendance> lstAttendance = await _context.Attendes
                .Include(a => a.RequestedBy)
                    .ThenInclude(e => e!.JobRole)
                .Include(a => a.Approver)
                    .ThenInclude(e => e!.JobRole)
                .Where(c => c.ProjectID == projectId && c.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE && c.TenantId == tenantId)
                .ToListAsync();

            List<ProjectAllocation> projectteam = await _projectServices.GetTeamByProject(tenantId, projectId, organizationId, true);
            var idList = projectteam.Select(p => p.EmployeeId).ToList();

            foreach (Attendance attende in lstAttendance)
            {
                var teamMember = projectteam.Find(m => m.EmployeeId == attende.EmployeeId);
                if (teamMember != null && teamMember.Employee != null && teamMember.Employee.JobRole != null)
                {
                    var result1 = new EmployeeAttendanceVM()
                    {
                        Id = attende.Id,
                        CheckInTime = attende.InTime,
                        CheckOutTime = attende.OutTime,
                        Activity = attende.Activity,
                        EmployeeAvatar = null,
                        EmployeeId = attende.EmployeeId,
                        FirstName = teamMember.Employee.FirstName,
                        LastName = teamMember.Employee.LastName,
                        JobRoleName = teamMember.Employee.JobRole.Name,
                        OrganizationName = teamMember.Employee.Organization?.Name,
                        ProjectId = projectId,
                        ProjectName = teamMember.Project?.Name,
                        ApprovedAt = attende.ApprovedAt,
                        Approver = _mapper.Map<BasicEmployeeVM>(attende.Approver),
                        RequestedAt = attende.RequestedAt,
                        RequestedBy = _mapper.Map<BasicEmployeeVM>(attende.RequestedBy)
                    };
                    result.Add(result1);
                }


            }

            result.Sort(delegate (EmployeeAttendanceVM x, EmployeeAttendanceVM y)
            {
                return string.Compare(x.FirstName, y.FirstName, StringComparison.Ordinal);
            });
            _logger.LogInfo("{count} Attendance records fetched successfully", result.Count);
            return Ok(ApiResponse<object>.SuccessResponse(result, System.String.Format("{0} Attendance records fetched successfully", result.Count), 200));
        }

        [HttpPost("record")]
        public async Task<IActionResult> RecordAttendance([FromBody] RecordAttendanceDot recordAttendanceDot)
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values
                    .SelectMany(v => v.Errors)
                    .Select(e => e.ErrorMessage)
                    .ToList();
                _logger.LogWarning("User sent Invalid Date while marking attendance \n {Error}", string.Join(",", errors));
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
            }

            using var scope = _serviceScopeFactory.CreateScope();
            var _signalR = scope.ServiceProvider.GetRequiredService<IHubContext<MarcoHub>>();
            var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
            var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

            var currentEmployee = await _userHelper.GetCurrentEmployeeAsync();
            using var transaction = await _context.Database.BeginTransactionAsync();

            try
            {
                Attendance? attendance = await _context.Attendes.FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == tenantId); ;

                if (recordAttendanceDot.MarkTime == null)
                {
                    _logger.LogWarning("User sent Invalid Mark Time while marking attendance");
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Mark Time", "Invalid Mark Time", 400));
                }

                DateTime finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime);
                if (recordAttendanceDot.Comment == null)
                {
                    _logger.LogWarning("User sent Invalid comment while marking attendance");
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Comment", "Invalid Comment", 400));
                }

                if (attendance != null)
                {
                    attendance.Comment = recordAttendanceDot.Comment;
                    if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_IN)
                    {
                        attendance.InTime = finalDateTime;
                        attendance.OutTime = null;
                        attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
                    }
                    else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_OUT)
                    {
                        attendance.IsApproved = true;
                        attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
                        attendance.OutTime = finalDateTime;
                    }
                    else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)
                    {
                        DateTime date = attendance.AttendanceDate;
                        finalDateTime = GetDateFromTimeStamp(date.Date, recordAttendanceDot.MarkTime);
                        if (attendance.InTime <= finalDateTime)
                        {
                            attendance.OutTime = finalDateTime;
                            attendance.Activity = ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE;
                            attendance.RequestedById = currentEmployee.Id;
                            attendance.RequestedAt = DateTime.UtcNow;
                        }
                        else
                        {
                            _logger.LogWarning("Employee {EmployeeId} sent regularization request but it check-out time is earlier than check-out");
                            return BadRequest(ApiResponse<object>.ErrorResponse("Check-out time must be later than check-in time", "Check-out time must be later than check-in time", 400));
                        }
                        // do nothing
                    }
                    else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REGULARIZE)
                    {
                        attendance.IsApproved = true;
                        attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
                        attendance.ApprovedById = currentEmployee.Id;
                        attendance.ApprovedAt = DateTime.UtcNow;
                        // do nothing
                    }
                    else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT)
                    {
                        attendance.IsApproved = false;
                        attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT;
                        attendance.ApprovedById = currentEmployee.Id;
                        attendance.ApprovedAt = DateTime.UtcNow;
                        // do nothing
                    }
                    attendance.Date = DateTime.UtcNow;

                    // update code
                    _context.Attendes.Update(attendance);
                }
                else
                {
                    attendance = new Attendance();
                    attendance.TenantId = tenantId;
                    attendance.AttendanceDate = recordAttendanceDot.Date;
                    // attendance.Activity = recordAttendanceDot.Action;
                    attendance.Comment = recordAttendanceDot.Comment;
                    attendance.EmployeeId = recordAttendanceDot.EmployeeID;
                    attendance.ProjectID = recordAttendanceDot.ProjectID;
                    attendance.Date = DateTime.UtcNow;



                    attendance.InTime = finalDateTime;
                    attendance.OutTime = null;
                    attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;

                    _context.Attendes.Add(attendance);
                }

                await _context.SaveChangesAsync();


                // Step 3: Always insert a new log entry
                var attendanceLog = new AttendanceLog
                {
                    AttendanceId = attendance.Id, // Use existing or new AttendanceId
                    Activity = attendance.Activity,

                    ActivityTime = finalDateTime,
                    Comment = recordAttendanceDot.Comment,
                    EmployeeID = recordAttendanceDot.EmployeeID,
                    Latitude = recordAttendanceDot.Latitude,
                    Longitude = recordAttendanceDot.Longitude,

                    TenantId = tenantId,
                    UpdatedBy = currentEmployee.Id,
                    UpdatedOn = recordAttendanceDot.Date
                };
                //if (recordAttendanceDot.Image != null && recordAttendanceDot.Image.Count > 0)
                //{
                //    attendanceLog.Photo = recordAttendanceDot.Image[0].Base64Data;
                //}


                _context.AttendanceLogs.Add(attendanceLog);
                await _context.SaveChangesAsync();

                await transaction.CommitAsync(); // Commit transaction

                Employee employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID);
                if (employee.JobRole != null)
                {
                    EmployeeAttendanceVM vm = new EmployeeAttendanceVM()
                    {
                        CheckInTime = attendance.InTime,
                        CheckOutTime = attendance.OutTime,
                        EmployeeAvatar = null,
                        EmployeeId = recordAttendanceDot.EmployeeID,
                        FirstName = employee.FirstName,
                        LastName = employee.LastName,
                        Id = attendance.Id,
                        Activity = attendance.Activity,
                        JobRoleName = employee.JobRole.Name,
                        OrganizationName = employee.Organization?.Name
                    };
                    var sendActivity = 0;
                    if (recordAttendanceDot.Id == Guid.Empty)
                    {
                        sendActivity = 1;
                    }
                    var notification = new { LoggedInUserId = currentEmployee.Id, Keyword = "Attendance", Activity = sendActivity, ProjectId = attendance.ProjectID, Response = vm };
                    await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
                    _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);

                    _ = 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 = $"{vm.FirstName} {vm.LastName}";

                        await _firebase.SendAttendanceMessageAsync(attendance.ProjectID, name, recordAttendanceDot.Action, attendance.EmployeeId, tenantId);

                    });

                    return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
                }
                _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
                return Ok(ApiResponse<object>.SuccessResponse(new EmployeeAttendanceVM(), "Attendance marked successfully.", 200));

            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync(); // Rollback on failure
                _logger.LogError(ex, "An Error occured while marking attendance");
                var response = new
                {
                    message = ex.Message,
                    detail = ex.StackTrace,
                    statusCode = StatusCodes.Status500InternalServerError
                };
                return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, response, 400));
            }

        }

        [HttpPost]
        [Route("record-image")]
        public async Task<IActionResult> RecordAttendanceWithImage([FromBody] RecordAttendanceDot recordAttendanceDot)
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
                _logger.LogWarning("Invalid attendance model received. \n {Error}", string.Join(",", errors));
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
            }

            using var scope = _serviceScopeFactory.CreateScope();
            var _s3Service = scope.ServiceProvider.GetRequiredService<S3UploadService>();
            var _signalR = scope.ServiceProvider.GetRequiredService<IHubContext<MarcoHub>>();
            var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
            var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
            var batchId = Guid.NewGuid();

            using var transaction = await _context.Database.BeginTransactionAsync();
            try
            {
                // Validate mark time
                if (recordAttendanceDot.MarkTime == null)
                {
                    _logger.LogWarning("Null mark time provided.");
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Mark Time", "Mark time is required", 400));
                }

                if (string.IsNullOrWhiteSpace(recordAttendanceDot.Comment))
                {
                    _logger.LogWarning("Empty comment provided.");
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Comment", "Comment is required", 400));
                }

                var finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime);
                var attendance = await _context.Attendes
                    .FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == tenantId);

                // Create or update attendance
                if (attendance == null)
                {
                    attendance = new Attendance
                    {
                        TenantId = tenantId,
                        AttendanceDate = recordAttendanceDot.Date,
                        Comment = recordAttendanceDot.Comment,
                        EmployeeId = recordAttendanceDot.EmployeeID,
                        ProjectID = recordAttendanceDot.ProjectID,
                        Date = DateTime.UtcNow,
                        InTime = finalDateTime,
                        Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT
                    };
                    _context.Attendes.Add(attendance);
                }
                else
                {
                    attendance.Comment = recordAttendanceDot.Comment;
                    attendance.Date = DateTime.UtcNow;

                    switch (recordAttendanceDot.Action)
                    {
                        case ATTENDANCE_MARK_TYPE.CHECK_IN:
                            attendance.InTime = finalDateTime;
                            attendance.OutTime = null;
                            attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
                            break;
                        case ATTENDANCE_MARK_TYPE.CHECK_OUT:
                            attendance.OutTime = finalDateTime;
                            attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
                            attendance.IsApproved = true;
                            break;
                        case ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE:
                            DateTime date = attendance.InTime ?? recordAttendanceDot.Date;
                            finalDateTime = GetDateFromTimeStamp(date.Date, recordAttendanceDot.MarkTime);
                            if (attendance.InTime < finalDateTime)
                            {
                                attendance.OutTime = finalDateTime;
                                attendance.Activity = ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE;
                                attendance.RequestedById = loggedInEmployee.Id;
                                attendance.RequestedAt = DateTime.UtcNow;
                            }
                            else
                            {
                                _logger.LogWarning("Regularization check-out time is before check-in.");
                                return BadRequest(ApiResponse<object>.ErrorResponse("Check-out time must be later than check-in time", "Invalid regularization", 400));
                            }
                            break;
                        case ATTENDANCE_MARK_TYPE.REGULARIZE:
                            attendance.IsApproved = true;
                            attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
                            attendance.ApprovedById = loggedInEmployee.Id;
                            attendance.ApprovedAt = DateTime.UtcNow;
                            break;
                        case ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT:
                            attendance.IsApproved = false;
                            attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT;
                            attendance.ApprovedById = loggedInEmployee.Id;
                            attendance.ApprovedAt = DateTime.UtcNow;
                            break;
                    }

                    _context.Attendes.Update(attendance);
                }

                // Upload image if present
                Document? document = null;
                string? preSignedUrl = null;

                if (recordAttendanceDot.Image != null && recordAttendanceDot.Image.ContentType != null)
                {
                    string base64 = recordAttendanceDot.Image.Base64Data?.Split(',').LastOrDefault() ?? "";
                    if (string.IsNullOrWhiteSpace(base64))
                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Image data missing", 400));

                    var fileType = _s3Service.GetContentTypeFromBase64(base64);
                    var fileName = _s3Service.GenerateFileName(fileType, tenantId, "attendance");
                    var objectKey = $"tenant-{tenantId}/Employee/{recordAttendanceDot.EmployeeID}/Attendance/{fileName}";

                    await _s3Service.UploadFileAsync(base64, fileType, objectKey);
                    preSignedUrl = _s3Service.GeneratePreSignedUrl(objectKey);

                    document = new Document
                    {
                        BatchId = batchId,
                        UploadedById = loggedInEmployee.Id,
                        FileName = recordAttendanceDot.Image.FileName ?? "",
                        ContentType = recordAttendanceDot.Image.ContentType,
                        S3Key = objectKey,
                        //Base64Data = recordAttendanceDot.Image.Base64Data,
                        FileSize = recordAttendanceDot.Image.FileSize,
                        UploadedAt = recordAttendanceDot.Date,
                        TenantId = tenantId
                    };

                    _context.Documents.Add(document);
                }

                // Log attendance
                var attendanceLog = new AttendanceLog
                {
                    AttendanceId = attendance.Id,
                    Activity = attendance.Activity,
                    ActivityTime = finalDateTime,
                    Comment = recordAttendanceDot.Comment,
                    EmployeeID = recordAttendanceDot.EmployeeID,
                    Latitude = recordAttendanceDot.Latitude,
                    Longitude = recordAttendanceDot.Longitude,
                    DocumentId = document?.Id,
                    TenantId = tenantId,
                    UpdatedBy = loggedInEmployee.Id,
                    UpdatedOn = recordAttendanceDot.Date
                };
                _context.AttendanceLogs.Add(attendanceLog);

                await _context.SaveChangesAsync();
                await transaction.CommitAsync();

                // Construct view model
                var employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID);
                var vm = new EmployeeAttendanceVM
                {
                    Id = attendance.Id,
                    EmployeeId = employee.Id,
                    FirstName = employee.FirstName,
                    LastName = employee.LastName,
                    CheckInTime = attendance.InTime,
                    CheckOutTime = attendance.OutTime,
                    Activity = attendance.Activity,
                    JobRoleName = employee.JobRole?.Name,
                    DocumentId = document?.Id ?? Guid.Empty,
                    ThumbPreSignedUrl = preSignedUrl ?? "",
                    PreSignedUrl = preSignedUrl ?? ""
                };

                var notification = new
                {
                    LoggedInUserId = loggedInEmployee.Id,
                    Keyword = "Attendance",
                    Activity = recordAttendanceDot.Id == Guid.Empty ? 1 : 0,
                    ProjectId = attendance.ProjectID,
                    Response = vm
                };

                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 = $"{vm.FirstName} {vm.LastName}";

                    await _firebase.SendAttendanceMessageAsync(attendance.ProjectID, name, recordAttendanceDot.Action, attendance.EmployeeId, tenantId);

                });

                _logger.LogInfo("Attendance recorded for employee: {FullName}", $"{employee.FirstName} {employee.LastName}");
                return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                _logger.LogError(ex, "Error while recording attendance");
                return BadRequest(ApiResponse<object>.ErrorResponse("Something went wrong", ex.Message, 500));
            }
        }

        private static DateTime GetDateFromTimeStamp(DateTime date, string timeString)
        {
            //DateTime date = recordAttendanceDot.Date;



            // Parse time string to TimeSpan
            DateTime parsedTime = DateTime.ParseExact(timeString, "hh:mm tt", CultureInfo.InvariantCulture);

            // Combine date with time
            DateTime finalDateTime = new DateTime(date.Year, date.Month, date.Day, parsedTime.Hour, parsedTime.Minute, 0);
            return finalDateTime;
        }

        /// <summary>
        /// Fetches attendance for an entire project team using a single, optimized database query.
        /// </summary>
        private async Task<List<EmployeeAttendanceVM>> GetTeamAttendanceAsync(Guid tenantId, Guid projectId, Guid? organizationId, DateTime forDate, bool includeInactive)
        {
            // This single query joins ProjectAllocations with Employees and performs a LEFT JOIN with Attendances.
            // This is far more efficient than fetching collections and joining them in memory.
            var query = _context.ProjectAllocations
                .Include(pa => pa.Project)
                .Include(pa => pa.Employee)
                    .ThenInclude(e => e!.Organization)
                .Include(pa => pa.Employee)
                    .ThenInclude(e => e!.JobRole)
                .Where(pa => pa.TenantId == tenantId && pa.ProjectId == projectId);

            // Apply filters based on optional parameters
            if (!includeInactive)
            {
                query = query.Where(pa => pa.IsActive);
            }
            if (organizationId.HasValue)
            {
                query = query.Where(pa => pa.Employee != null && pa.Employee.OrganizationId == organizationId);
            }

            List<Attendance> lstAttendance = await _context.Attendes
                .Include(a => a.RequestedBy)
                    .ThenInclude(e => e!.JobRole)
                .Include(a => a.Approver)
                    .ThenInclude(e => e!.JobRole)
                .Where(c => c.ProjectID == projectId && c.AttendanceDate.Date == forDate && c.TenantId == tenantId).ToListAsync();

            var teamAttendance = await query
                .AsNoTracking()
                .ToListAsync();

            var response = teamAttendance
                .Select(teamMember =>
                {
                    var result1 = new EmployeeAttendanceVM()
                    {
                        EmployeeAvatar = null,
                        EmployeeId = teamMember.EmployeeId,
                        FirstName = teamMember.Employee?.FirstName,
                        LastName = teamMember.Employee?.LastName,
                        OrganizationName = teamMember.Employee?.Organization?.Name,
                        JobRoleName = teamMember.Employee?.JobRole?.Name,
                        ProjectId = projectId,
                        ProjectName = teamMember.Project?.Name
                    };

                    var attendance = lstAttendance.Find(x => x.EmployeeId == teamMember.EmployeeId) ?? new Attendance();
                    if (attendance != null)
                    {
                        result1.Id = attendance.Id;
                        result1.CheckInTime = attendance.InTime;
                        result1.CheckOutTime = attendance.OutTime;
                        result1.Activity = attendance.Activity;
                        result1.ApprovedAt = attendance.ApprovedAt;
                        result1.Approver = _mapper.Map<BasicEmployeeVM>(attendance.Approver);
                        result1.RequestedAt = attendance.RequestedAt;
                        result1.RequestedBy = _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy);
                    }
                    return result1;
                })
                .OrderBy(vm => vm.FirstName) // Let the database handle sorting.
                .ThenBy(vm => vm.LastName).ToList();

            return response;
        }

        /// <summary>
        /// Fetches a single attendance record for the logged-in employee.
        /// </summary>
        private async Task<List<EmployeeAttendanceVM>> GetSelfAttendanceAsync(Guid tenantId, Guid projectId, Guid employeeId, Guid? organizationId, DateTime forDate)
        {
            List<EmployeeAttendanceVM> result = new List<EmployeeAttendanceVM>();

            // This query fetches the employee's project allocation and their attendance in a single trip.
            Attendance lstAttendance = await _context.Attendes
                .Include(a => a.RequestedBy)
                    .ThenInclude(e => e!.JobRole)
                .Include(a => a.Approver)
                    .ThenInclude(e => e!.JobRole)
                    .FirstOrDefaultAsync(c => c.ProjectID == projectId && c.EmployeeId == employeeId && c.AttendanceDate.Date == forDate && c.TenantId == tenantId) ?? new Attendance();

            var projectAllocationQuery = _context.ProjectAllocations
                .Include(pa => pa.Project)
                .Include(pa => pa.Employee)
                    .ThenInclude(e => e!.Organization)
                .Include(pa => pa.Employee)
                    .ThenInclude(e => e!.JobRole)
                .Where(pa => pa.EmployeeId == employeeId && pa.TenantId == tenantId && pa.IsActive &&
                    pa.ProjectId == projectId && pa.Project != null &&
                    pa.Employee != null && pa.Employee.Organization != null && pa.Employee.JobRole != null);

            if (organizationId.HasValue)
            {
                projectAllocationQuery = projectAllocationQuery.Where(pa => pa.Employee != null && pa.Employee.OrganizationId == organizationId);
            }

            var projectAllocation = await projectAllocationQuery.FirstOrDefaultAsync();

            if (projectAllocation != null)
            {
                EmployeeAttendanceVM result1 = new EmployeeAttendanceVM
                {
                    Id = lstAttendance.Id,
                    EmployeeAvatar = null,
                    EmployeeId = projectAllocation.EmployeeId,
                    FirstName = projectAllocation.Employee?.FirstName,
                    OrganizationName = projectAllocation.Employee?.Organization?.Name,
                    LastName = projectAllocation.Employee?.LastName,
                    JobRoleName = projectAllocation.Employee?.JobRole?.Name,
                    ProjectId = projectId,
                    ProjectName = projectAllocation.Project?.Name,
                    CheckInTime = lstAttendance.InTime,
                    CheckOutTime = lstAttendance.OutTime,
                    Activity = lstAttendance.Activity,
                    ApprovedAt = lstAttendance.ApprovedAt,
                    Approver = _mapper.Map<BasicEmployeeVM>(lstAttendance.Approver),
                    RequestedAt = lstAttendance.RequestedAt,
                    RequestedBy = _mapper.Map<BasicEmployeeVM>(lstAttendance.RequestedBy)
                };
                result.Add(result1);
            }

            return result;
        }

    }
}
