// EN: Handler for FindAvailableSlotsQuery - Core business logic for slot finding.
// VI: Handler cho FindAvailableSlotsQuery - Logic nghiệp vụ chính để tìm slot.

using BookingService.API.Application.DTOs;
using BookingService.Infrastructure;
using BookingService.Infrastructure.Repositories;
using MediatR;
using Microsoft.EntityFrameworkCore;

namespace BookingService.API.Application.Queries;

/// <summary>
/// EN: Implements the slot finding algorithm: Available Slots = Staff Schedule ∩ Resource Availability - Booked Appointments
/// VI: Triển khai thuật toán tìm slot: Slot Khả dụng = Lịch Nhân viên ∩ Tài nguyên - Cuộc hẹn đã đặt
/// </summary>
public class FindAvailableSlotsQueryHandler : IRequestHandler<FindAvailableSlotsQuery, List<TimeSlotDto>>
{
    private readonly BookingContext _context;
    private readonly IStaffScheduleRepository _staffScheduleRepository;
    private readonly ILogger<FindAvailableSlotsQueryHandler> _logger;

    public FindAvailableSlotsQueryHandler(
        BookingContext context,
        IStaffScheduleRepository staffScheduleRepository,
        ILogger<FindAvailableSlotsQueryHandler> logger)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
        _staffScheduleRepository = staffScheduleRepository ?? throw new ArgumentNullException(nameof(staffScheduleRepository));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<List<TimeSlotDto>> Handle(FindAvailableSlotsQuery request, CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Finding available slots for shop {ShopId}, service {ServiceId}, date {Date}",
            request.ShopId, request.ServiceId, request.Date);

        var dayOfWeek = (int)request.Date.DayOfWeek;
        var availableSlots = new List<TimeSlotDto>();

        // EN: Get staff schedules for the day
        // VI: Lấy lịch nhân viên cho ngày
        var staffSchedules = await _staffScheduleRepository.GetByShopIdAndDayAsync(
            request.ShopId, dayOfWeek, cancellationToken);

        // EN: Filter by specific staff if requested
        // VI: Lọc theo nhân viên cụ thể nếu được yêu cầu
        if (request.StaffId.HasValue)
        {
            staffSchedules = staffSchedules.Where(s => s.StaffId == request.StaffId.Value).ToList();
        }

        // EN: Get existing appointments for the date
        // VI: Lấy các cuộc hẹn hiện tại trong ngày
        var startOfDay = request.Date.Date;
        var endOfDay = startOfDay.AddDays(1);
        var existingAppointments = await _context.Appointments
            .Where(a => a.ShopId == request.ShopId && 
                       a.StartTime >= startOfDay && 
                       a.StartTime < endOfDay &&
                       a.Status != "Cancelled")
            .ToListAsync(cancellationToken);

        // EN: For each staff, generate time slots
        // VI: Với mỗi nhân viên, tạo các slot thời gian
        foreach (var schedule in staffSchedules)
        {
            var workStart = new DateTime(request.Date.Year, request.Date.Month, request.Date.Day,
                schedule.StartTime.Hour, schedule.StartTime.Minute, 0);
            var workEnd = new DateTime(request.Date.Year, request.Date.Month, request.Date.Day,
                schedule.EndTime.Hour, schedule.EndTime.Minute, 0);

            // EN: Generate 15-minute intervals within working hours
            // VI: Tạo các khoảng 15 phút trong giờ làm việc
            var currentTime = workStart;
            while (currentTime.AddMinutes(request.ServiceDurationMinutes) <= workEnd)
            {
                var slotEnd = currentTime.AddMinutes(request.ServiceDurationMinutes);

                // EN: Check if this slot conflicts with any existing appointment
                // VI: Kiểm tra xem slot này có xung đột với cuộc hẹn nào không
                var hasConflict = existingAppointments.Any(a =>
                    a.StaffId == schedule.StaffId &&
                    ((currentTime >= a.StartTime && currentTime < a.EndTime) ||
                     (slotEnd > a.StartTime && slotEnd <= a.EndTime) ||
                     (currentTime <= a.StartTime && slotEnd >= a.EndTime)));

                if (!hasConflict)
                {
                    availableSlots.Add(new TimeSlotDto
                    {
                        StartTime = currentTime,
                        EndTime = slotEnd,
                        StaffId = schedule.StaffId,
                        ResourceId = request.ResourceId
                    });
                }

                // EN: Move to next 15-minute interval
                // VI: Chuyển sang khoảng 15 phút tiếp theo
                currentTime = currentTime.AddMinutes(15);
            }
        }

        _logger.LogInformation("Found {Count} available slots", availableSlots.Count);

        return availableSlots.OrderBy(s => s.StartTime).ToList();
    }
}
