using Microsoft.EntityFrameworkCore;
using UniVerse.Application.DTOs.Common;
using UniVerse.Application.DTOs.Courses;
using UniVerse.Application.Interfaces;
using UniVerse.Application.Mappings;
using UniVerse.Domain.Entities;
using UniVerse.Domain.Exceptions;
using UniVerse.Infrastructure.Data;

namespace UniVerse.Infrastructure.Services;

public class CourseService : ICourseService
{
    private readonly AppDbContext _db;
    public CourseService(AppDbContext db) => _db = db;

    public async Task<PagedResult<CourseDto>> GetAllAsync(CourseFilterRequest filter)
    {
        var query = _db.Courses.Include(c => c.CourseTags).ThenInclude(ct => ct.Tag).AsQueryable();
        if (!string.IsNullOrEmpty(filter.Search))
            query = query.Where(c => c.Name.ToLower().Contains(filter.Search.ToLower()));
        if (filter.IsSynced.HasValue)
            query = query.Where(c => c.IsSynced == filter.IsSynced.Value);
        if (filter.TagId.HasValue)
            query = query.Where(c => c.CourseTags.Any(ct => ct.TagId == filter.TagId.Value));

        var total = await query.CountAsync();
        var courses = await query.OrderByDescending(c => c.CreatedAt)
            .Skip((filter.Page - 1) * filter.PageSize).Take(filter.PageSize).ToListAsync();
        return PagedResult<CourseDto>.Create(courses.Select(c => c.ToDto()).ToList(), total, filter.Page, filter.PageSize);
    }

    public async Task<CourseDto> GetByIdAsync(int id)
    {
        var course = await _db.Courses.Include(c => c.CourseTags).ThenInclude(ct => ct.Tag)
            .FirstOrDefaultAsync(c => c.Id == id) ?? throw new NotFoundException("Course", id);
        return course.ToDto();
    }

    public async Task<CourseDto> CreateAsync(CreateCourseRequest request)
    {
        var course = new Course { Name = request.Name, Description = request.Description };
        _db.Courses.Add(course);
        await _db.SaveChangesAsync();
        return await GetByIdAsync(course.Id);
    }

    public async Task<CourseDto> UpdateAsync(int id, UpdateCourseRequest request)
    {
        var course = await _db.Courses.FindAsync(id) ?? throw new NotFoundException("Course", id);
        course.Name = request.Name;
        course.Description = request.Description;
        course.UpdatedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync();
        return await GetByIdAsync(id);
    }

    public async Task DeleteAsync(int id)
    {
        var course = await _db.Courses.FindAsync(id) ?? throw new NotFoundException("Course", id);
        _db.Courses.Remove(course);
        await _db.SaveChangesAsync();
    }

    public async Task AddTagAsync(int courseId, int tagId)
    {
        if (await _db.CourseTags.AnyAsync(ct => ct.CourseId == courseId && ct.TagId == tagId))
            throw new ConflictException("Tag already linked to course.");
        _ = await _db.Courses.FindAsync(courseId) ?? throw new NotFoundException("Course", courseId);
        _ = await _db.Tags.FindAsync(tagId) ?? throw new NotFoundException("Tag", tagId);
        _db.CourseTags.Add(new CourseTag { CourseId = courseId, TagId = tagId });
        await _db.SaveChangesAsync();
    }

    public async Task RemoveTagAsync(int courseId, int tagId)
    {
        var ct = await _db.CourseTags.FirstOrDefaultAsync(x => x.CourseId == courseId && x.TagId == tagId)
            ?? throw new NotFoundException("CourseTag not found.");
        _db.CourseTags.Remove(ct);
        await _db.SaveChangesAsync();
    }
}
