namespace Domain.Services;

using Domain.Entities;
using Domain.Exceptions;

/// <summary>
/// Domain service for enrollment business rules.
/// Contains validation logic that spans multiple aggregates.
/// </summary>
public class EnrollmentDomainService
{
    /// <summary>
    /// Validates that a student can enroll in a subject.
    /// </summary>
    /// <param name="student">The student attempting to enroll.</param>
    /// <param name="subject">The subject to enroll in.</param>
    /// <exception cref="MaxEnrollmentsExceededException">Student has reached max enrollments.</exception>
    /// <exception cref="SameProfessorConstraintException">Student already has a subject with this professor.</exception>
    /// <exception cref="DuplicateEnrollmentException">Student is already enrolled in this subject.</exception>
    public void ValidateEnrollment(Student student, Subject subject)
    {
        if (!student.CanEnroll())
            throw new MaxEnrollmentsExceededException(student.Id);

        if (student.HasProfessor(subject.ProfessorId))
            throw new SameProfessorConstraintException(student.Id, subject.ProfessorId);

        if (student.Enrollments.Any(e => e.SubjectId == subject.Id))
            throw new DuplicateEnrollmentException(student.Id, subject.Id);
    }

    /// <summary>
    /// Creates an enrollment after validating business rules.
    /// </summary>
    /// <param name="student">The student to enroll.</param>
    /// <param name="subject">The subject to enroll in.</param>
    /// <returns>The created enrollment.</returns>
    public Enrollment CreateEnrollment(Student student, Subject subject)
    {
        ValidateEnrollment(student, subject);

        var enrollment = new Enrollment(student.Id, subject.Id);
        student.AddEnrollment(enrollment);

        return enrollment;
    }

    /// <summary>
    /// Checks if a subject is available for enrollment by a student.
    /// Returns availability status and reason without throwing exceptions.
    /// </summary>
    /// <param name="student">The student checking availability.</param>
    /// <param name="subject">The subject to check.</param>
    /// <returns>A tuple with availability status and optional reason if unavailable.</returns>
    public (bool IsAvailable, string? Reason) CheckSubjectAvailability(
        Student student,
        Subject subject)
    {
        // Already enrolled in this subject
        if (student.Enrollments.Any(e => e.SubjectId == subject.Id))
            return (false, "Already enrolled");

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

        // Already has a subject with this professor
        if (student.HasProfessor(subject.ProfessorId))
            return (false, "Already have a subject with this professor");

        return (true, null);
    }
}
