namespace Application.Subjects.Queries;

using Application.Subjects.DTOs;
using Domain.Entities;
using Domain.Exceptions;
using Domain.Ports.Repositories;
using MediatR;

public record GetAvailableSubjectsQuery(int StudentId) : IRequest<IReadOnlyList<AvailableSubjectDto>>;

public class GetAvailableSubjectsHandler(
    IStudentRepository studentRepository,
    ISubjectRepository subjectRepository)
    : IRequestHandler<GetAvailableSubjectsQuery, IReadOnlyList<AvailableSubjectDto>>
{
    public async Task<IReadOnlyList<AvailableSubjectDto>> Handle(
        GetAvailableSubjectsQuery request,
        CancellationToken ct)
    {
        var student = await studentRepository.GetByIdWithEnrollmentsAsync(request.StudentId, ct)
            ?? throw new StudentNotFoundException(request.StudentId);

        var allSubjects = await subjectRepository.GetAllWithProfessorsAsync(ct);

        var enrolledSubjectIds = student.Enrollments.Select(e => e.SubjectId).ToHashSet();
        var enrolledProfessorIds = student.Enrollments
            .Select(e => e.Subject?.ProfessorId ?? 0)
            .Where(id => id > 0)
            .ToHashSet();

        return allSubjects.Select(s =>
        {
            var (isAvailable, reason) = GetAvailability(s, student, enrolledSubjectIds, enrolledProfessorIds);
            return new AvailableSubjectDto(
                s.Id,
                s.Name,
                s.Credits,
                s.ProfessorId,
                s.Professor?.Name ?? "",
                isAvailable,
                reason);
        }).ToList();
    }

    private static (bool IsAvailable, string? Reason) GetAvailability(
        Subject subject,
        Student student,
        HashSet<int> enrolledSubjectIds,
        HashSet<int> enrolledProfessorIds)
    {
        if (enrolledSubjectIds.Contains(subject.Id))
            return (false, "Already enrolled");

        if (!student.CanEnroll())
            return (false, "Maximum 3 subjects reached");

        if (enrolledProfessorIds.Contains(subject.ProfessorId))
            return (false, "Already have a subject with this professor");

        return (true, null);
    }
}
