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

namespace UniVerse.Infrastructure.Services;

public class ReviewService : IReviewService
{
    private readonly AppDbContext _db;
    private readonly IGamificationService _gamification;
    private readonly IReviewAnalysisQueue _reviewAnalysisQueue;

    public ReviewService(
        AppDbContext db,
        IGamificationService gamification,
        IReviewAnalysisQueue reviewAnalysisQueue)
    {
        _db = db;
        _gamification = gamification;
        _reviewAnalysisQueue = reviewAnalysisQueue;
    }

    private IQueryable<Review> BaseQuery() => _db.Reviews
        .Include(r => r.Lecture).Include(r => r.User);

    public async Task<ReviewDto> CreateAsync(int userId, CreateReviewRequest req)
    {
        _ = await _db.Lectures.FindAsync(req.LectureId) ?? throw new NotFoundException("Lecture", req.LectureId);
        if (await _db.Reviews.AnyAsync(r => r.LectureId == req.LectureId && r.UserId == userId))
            throw new ConflictException("You already reviewed this lecture.");
        var review = new Review
        {
            LectureId = req.LectureId, UserId = userId,
            Rating = req.Rating, Text = req.Text,
            LlmStatus = ReviewLlmStatus.Pending
        };
        _db.Reviews.Add(review);
        await _db.SaveChangesAsync();
        await _gamification.CheckAndAwardAchievementsAsync(userId);
        await _reviewAnalysisQueue.EnqueueAsync(review.Id);
        var full = await BaseQuery().FirstAsync(r => r.Id == review.Id);
        return full.ToDto();
    }

    public async Task<ReviewDto> GetByIdAsync(int id)
    {
        var review = await BaseQuery().FirstOrDefaultAsync(r => r.Id == id)
            ?? throw new NotFoundException("Review", id);
        return review.ToDto();
    }

    public async Task<ReviewDto> UpdateAsync(int id, int userId, UpdateReviewRequest req)
    {
        var review = await _db.Reviews.FindAsync(id) ?? throw new NotFoundException("Review", id);
        if (review.UserId != userId) throw new ForbiddenException();
        review.Rating = req.Rating; review.Text = req.Text;
        ResetLlmAnalysis(review);
        review.UpdatedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync();
        await _reviewAnalysisQueue.EnqueueAsync(review.Id);
        return await GetByIdAsync(id);
    }

    public async Task DeleteAsync(int id, int userId, bool isAdmin = false)
    {
        var review = await _db.Reviews.FindAsync(id) ?? throw new NotFoundException("Review", id);
        if (review.UserId != userId && !isAdmin) throw new ForbiddenException();
        _db.Reviews.Remove(review);
        await _db.SaveChangesAsync();
    }

    public async Task<PagedResult<ReviewDto>> GetByLectureAsync(
        int lectureId,
        PaginationRequest pagination,
        int? currentUserId = null,
        bool isAdmin = false)
    {
        if (!isAdmin)
        {
            if (!currentUserId.HasValue)
                throw new ForbiddenException();

            var lecture = await _db.Lectures.FirstOrDefaultAsync(l => l.Id == lectureId)
                ?? throw new NotFoundException("Lecture", lectureId);
            if (lecture.TeacherId != currentUserId.Value)
                throw new ForbiddenException("Teacher can access reviews only for their own lectures.");
        }

        var query = BaseQuery().Where(r => r.LectureId == lectureId);
        var total = await query.CountAsync();
        var items = await query.OrderByDescending(r => r.CreatedAt)
            .Skip((pagination.Page - 1) * pagination.PageSize).Take(pagination.PageSize).ToListAsync();
        return PagedResult<ReviewDto>.Create(items.Select(r => r.ToDto()).ToList(), total, pagination.Page, pagination.PageSize);
    }

    public async Task<PagedResult<ReviewDto>> GetByUserAsync(int userId, PaginationRequest pagination)
    {
        var query = BaseQuery().Where(r => r.UserId == userId);
        var total = await query.CountAsync();
        var items = await query.OrderByDescending(r => r.CreatedAt)
            .Skip((pagination.Page - 1) * pagination.PageSize).Take(pagination.PageSize).ToListAsync();
        return PagedResult<ReviewDto>.Create(items.Select(r => r.ToDto()).ToList(), total, pagination.Page, pagination.PageSize);
    }

    public async Task<PagedResult<ReviewDto>> GetAllAsync(ReviewFilterRequest filter)
    {
        var query = BaseQuery();
        if (filter.LlmStatus.HasValue)
            query = query.Where(r => r.LlmStatus == filter.LlmStatus.Value);

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

    public async Task ReanalyzeAsync(int id)
    {
        var review = await _db.Reviews.FindAsync(id) ?? throw new NotFoundException("Review", id);
        ResetLlmAnalysis(review);
        review.UpdatedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync();
        await _reviewAnalysisQueue.EnqueueAsync(review.Id);
    }

    private static void ResetLlmAnalysis(Review review)
    {
        review.LlmStatus = ReviewLlmStatus.Pending;
        review.Sentiment = null;
        review.QualityScore = null;
        review.IsInformative = null;
        review.LlmTags = null;
        review.LlmRawOutput = null;
    }
}
