namespace Adapters.Driven.Persistence.Repositories;

using Adapters.Driven.Persistence.Context;
using Domain.Entities;
using Domain.Ports.Repositories;
using Microsoft.EntityFrameworkCore;

public class EnrollmentRepository(AppDbContext context) : IEnrollmentRepository
{
    public async Task<Enrollment?> GetByIdAsync(int id, CancellationToken ct = default) =>
        await context.Enrollments.FindAsync([id], ct);

    public async Task<Enrollment?> GetByStudentAndSubjectAsync(int studentId, int subjectId, CancellationToken ct = default) =>
        await context.Enrollments
            .FirstOrDefaultAsync(e => e.StudentId == studentId && e.SubjectId == subjectId, ct);

    public async Task<IReadOnlyList<Enrollment>> GetByStudentIdAsync(int studentId, CancellationToken ct = default) =>
        await context.Enrollments
            .Include(e => e.Subject)
                .ThenInclude(s => s.Professor)
            .Where(e => e.StudentId == studentId)
            .AsNoTracking()
            .ToListAsync(ct);

    public async Task<IReadOnlyList<Enrollment>> GetBySubjectIdAsync(int subjectId, CancellationToken ct = default) =>
        await context.Enrollments
            .Include(e => e.Student)
            .Where(e => e.SubjectId == subjectId)
            .AsNoTracking()
            .ToListAsync(ct);

    public async Task<IReadOnlyList<Student>> GetClassmatesAsync(int studentId, int subjectId, CancellationToken ct = default) =>
        await context.Enrollments
            .Where(e => e.SubjectId == subjectId && e.StudentId != studentId)
            .Select(e => e.Student)
            .AsNoTracking()
            .ToListAsync(ct);

    public async Task<IReadOnlyDictionary<int, IReadOnlyList<Student>>> GetClassmatesBatchAsync(
        int studentId, IEnumerable<int> subjectIds, CancellationToken ct = default)
    {
        var subjectIdList = subjectIds.ToList();
        if (subjectIdList.Count == 0)
            return new Dictionary<int, IReadOnlyList<Student>>();

        // Single query to get all classmates for all subjects
        var enrollments = await context.Enrollments
            .Where(e => subjectIdList.Contains(e.SubjectId) && e.StudentId != studentId)
            .Select(e => new { e.SubjectId, e.Student.Id, e.Student.Name })
            .AsNoTracking()
            .ToListAsync(ct);

        // Group by SubjectId and project to Student (minimal data needed)
        return enrollments
            .GroupBy(e => e.SubjectId)
            .ToDictionary(
                g => g.Key,
                g => (IReadOnlyList<Student>)g
                    .Select(e => CreateMinimalStudent(e.Id, e.Name))
                    .ToList());
    }

    private static Student CreateMinimalStudent(int id, string name)
    {
        // Use reflection to set Id since it's private set
        var student = (Student)System.Runtime.CompilerServices.RuntimeHelpers
            .GetUninitializedObject(typeof(Student));
        typeof(Student).GetProperty("Id")!.SetValue(student, id);
        typeof(Student).GetProperty("Name")!.SetValue(student, name);
        return student;
    }

    public void Add(Enrollment enrollment) => context.Enrollments.Add(enrollment);
    public void Delete(Enrollment enrollment) => context.Enrollments.Remove(enrollment);
}
