namespace Adapters.Driven.Persistence.Repositories;

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

public class SubjectRepository(AppDbContext context) : ISubjectRepository
{
    public async Task<Subject?> GetByIdAsync(int id, CancellationToken ct = default) =>
        await context.Subjects.FindAsync([id], ct);

    public async Task<Subject?> GetByIdWithProfessorAsync(int id, CancellationToken ct = default) =>
        await context.Subjects
            .Include(s => s.Professor)
            .FirstOrDefaultAsync(s => s.Id == id, ct);

    public async Task<IReadOnlyList<Subject>> GetAllAsync(CancellationToken ct = default) =>
        await context.Subjects.AsNoTracking().ToListAsync(ct);

    public async Task<IReadOnlyList<Subject>> GetAllWithProfessorsAsync(CancellationToken ct = default) =>
        await context.Subjects
            .Include(s => s.Professor)
            .AsNoTracking()
            .ToListAsync(ct);

    public async Task<IReadOnlyList<Subject>> GetAvailableForStudentAsync(int studentId, CancellationToken ct = default)
    {
        // Optimized: single query instead of two separate queries
        var enrollmentData = await context.Enrollments
            .Where(e => e.StudentId == studentId)
            .Select(e => new { e.SubjectId, e.Subject.ProfessorId })
            .ToListAsync(ct);

        var enrolledSubjectIds = enrollmentData.Select(e => e.SubjectId).ToHashSet();
        var enrolledProfessorIds = enrollmentData.Select(e => e.ProfessorId).ToHashSet();

        return await context.Subjects
            .Include(s => s.Professor)
            .Where(s => !enrolledSubjectIds.Contains(s.Id) && !enrolledProfessorIds.Contains(s.ProfessorId))
            .AsNoTracking()
            .ToListAsync(ct);
    }

    // Direct projection - only SELECT needed columns
    public async Task<IReadOnlyList<TResult>> GetAllProjectedAsync<TResult>(
        Expression<Func<Subject, TResult>> selector,
        CancellationToken ct = default) =>
        await context.Subjects
            .AsNoTracking()
            .Select(selector)
            .ToListAsync(ct);
}
