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

namespace UniVerse.Api.Tests.Reviews;

public class ReviewServiceTests
{
    [Fact]
    public async Task CreateAsync_EnqueuesReviewAnalysis()
    {
        await using var db = CreateDbContext();
        var queue = Substitute.For<IReviewAnalysisQueue>();
        var service = CreateService(db, queue);
        await SeedLectureAsync(db);

        var result = await service.CreateAsync(1, new CreateReviewRequest(1, ReviewRating.Like, "Great lecture"));

        await queue.Received(1).EnqueueAsync(result.Id, Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task UpdateAsync_ResetsAnalysisAndEnqueuesReview()
    {
        await using var db = CreateDbContext();
        var queue = Substitute.For<IReviewAnalysisQueue>();
        var service = CreateService(db, queue);
        await SeedAnalyzedReviewAsync(db);

        await service.UpdateAsync(1, 1, new UpdateReviewRequest(ReviewRating.Neutral, "Updated text"));

        var review = await db.Reviews.SingleAsync(r => r.Id == 1);
        Assert.Equal(ReviewLlmStatus.Pending, review.LlmStatus);
        Assert.Null(review.Sentiment);
        Assert.Null(review.QualityScore);
        Assert.Null(review.IsInformative);
        Assert.Null(review.LlmTags);
        Assert.Null(review.LlmRawOutput);
        await queue.Received(1).EnqueueAsync(1, Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task ReanalyzeAsync_ResetsAnalysisAndEnqueuesReview()
    {
        await using var db = CreateDbContext();
        var queue = Substitute.For<IReviewAnalysisQueue>();
        var service = CreateService(db, queue);
        await SeedAnalyzedReviewAsync(db);

        await service.ReanalyzeAsync(1);

        var review = await db.Reviews.SingleAsync(r => r.Id == 1);
        Assert.Equal(ReviewLlmStatus.Pending, review.LlmStatus);
        Assert.Null(review.Sentiment);
        Assert.Null(review.QualityScore);
        Assert.Null(review.IsInformative);
        Assert.Null(review.LlmTags);
        Assert.Null(review.LlmRawOutput);
        await queue.Received(1).EnqueueAsync(1, Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task GetByLectureAsync_TeacherCannotReadAnotherTeachersReviews()
    {
        await using var db = CreateDbContext();
        var service = CreateService(db, Substitute.For<IReviewAnalysisQueue>());
        await SeedAnalyzedReviewAsync(db, teacherId: 2);

        await Assert.ThrowsAsync<ForbiddenException>(() =>
            service.GetByLectureAsync(1, new PaginationRequest(), currentUserId: 1));
    }

    [Fact]
    public async Task GetByLectureAsync_AdminCanReadAnyLectureReviews()
    {
        await using var db = CreateDbContext();
        var service = CreateService(db, Substitute.For<IReviewAnalysisQueue>());
        await SeedAnalyzedReviewAsync(db, teacherId: 2);

        var result = await service.GetByLectureAsync(1, new PaginationRequest(), currentUserId: 1, isAdmin: true);

        Assert.Single(result.Items);
    }

    private static ReviewService CreateService(AppDbContext db, IReviewAnalysisQueue queue)
    {
        var gamification = Substitute.For<IGamificationService>();
        gamification.CheckAndAwardAchievementsAsync(Arg.Any<int>()).Returns(Task.CompletedTask);
        return new ReviewService(db, gamification, queue);
    }

    private static async Task SeedLectureAsync(AppDbContext db, int? teacherId = null)
    {
        db.Users.Add(new User { Id = 1, Email = "student@test.local", DisplayName = "Student" });
        db.Courses.Add(new Course { Id = 1, Name = "Course" });
        db.Lectures.Add(new Lecture
        {
            Id = 1,
            CourseId = 1,
            TeacherId = teacherId,
            Title = "Lecture",
            StartsAt = DateTime.UtcNow.AddDays(-1),
            EndsAt = DateTime.UtcNow.AddDays(-1).AddHours(2),
            IsOpen = true,
            MaxEnrollments = 30
        });
        await db.SaveChangesAsync();
    }

    private static async Task SeedAnalyzedReviewAsync(AppDbContext db, int? teacherId = null)
    {
        await SeedLectureAsync(db, teacherId);
        db.Reviews.Add(new Review
        {
            Id = 1,
            LectureId = 1,
            UserId = 1,
            Rating = ReviewRating.Like,
            Text = "Original text",
            LlmStatus = ReviewLlmStatus.Analyzed,
            Sentiment = ReviewSentiment.Positive,
            QualityScore = 0.9,
            IsInformative = true,
            LlmTags = ["clear"],
            LlmRawOutput = "{\"quality_score\":0.9}"
        });
        await db.SaveChangesAsync();
    }

    private static AppDbContext CreateDbContext()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase($"ReviewServiceTests_{Guid.NewGuid()}")
            .Options;
        return new AppDbContext(options);
    }
}
