using Microsoft.EntityFrameworkCore;
using UniVerse.Application.DTOs.Courses;
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.Courses;

public class CourseServiceTests
{
    [Fact]
    public async Task GetAllAsync_AppliesSearchSyncedTagFiltersAndPagination()
    {
        await using var db = CreateDbContext();
        db.Tags.AddRange(
            new Tag { Id = 1, Name = "Backend", Type = TagType.Subject },
            new Tag { Id = 2, Name = "Frontend", Type = TagType.Subject });
        db.Courses.AddRange(
            Course(1, "ASP.NET Core", true, new DateTime(2026, 1, 3, 0, 0, 0, DateTimeKind.Utc)),
            Course(2, "Vue Basics", true, new DateTime(2026, 1, 2, 0, 0, 0, DateTimeKind.Utc)),
            Course(3, "Advanced ASP.NET", false, new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc)));
        db.CourseTags.AddRange(
            new CourseTag { CourseId = 1, TagId = 1 },
            new CourseTag { CourseId = 2, TagId = 2 },
            new CourseTag { CourseId = 3, TagId = 1 });
        await db.SaveChangesAsync();
        var service = new CourseService(db);

        var result = await service.GetAllAsync(new CourseFilterRequest(
            TagId: 1,
            Search: "asp",
            IsSynced: true,
            Page: 1,
            PageSize: 10));

        var item = Assert.Single(result.Items);
        Assert.Equal(1, item.Id);
        Assert.Equal(1, result.TotalCount);
        Assert.Equal(1, result.TotalPages);
        Assert.Equal("Backend", Assert.Single(item.Tags).Name);
    }

    [Fact]
    public async Task GetAllAsync_ReturnsRequestedPageMetadata()
    {
        await using var db = CreateDbContext();
        db.Courses.AddRange(
            Course(1, "Old", false, new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc)),
            Course(2, "Middle", false, new DateTime(2026, 1, 2, 0, 0, 0, DateTimeKind.Utc)),
            Course(3, "Newest", false, new DateTime(2026, 1, 3, 0, 0, 0, DateTimeKind.Utc)));
        await db.SaveChangesAsync();
        var service = new CourseService(db);

        var result = await service.GetAllAsync(new CourseFilterRequest(null, null, null, Page: 2, PageSize: 1));

        Assert.Equal(3, result.TotalCount);
        Assert.Equal(2, result.Page);
        Assert.Equal(1, result.PageSize);
        Assert.Equal(3, result.TotalPages);
        Assert.Equal(2, Assert.Single(result.Items).Id);
    }

    [Fact]
    public async Task AddTagAsync_LinksExistingCourseAndTag()
    {
        await using var db = CreateDbContext();
        db.Courses.Add(Course(1, "Course", false, DateTime.UtcNow));
        db.Tags.Add(new Tag { Id = 10, Name = "Tag", Type = TagType.Topic });
        await db.SaveChangesAsync();
        var service = new CourseService(db);

        await service.AddTagAsync(1, 10);

        Assert.True(await db.CourseTags.AnyAsync(ct => ct.CourseId == 1 && ct.TagId == 10));
    }

    [Fact]
    public async Task AddTagAsync_ThrowsWhenTagAlreadyLinked()
    {
        await using var db = CreateDbContext();
        db.Courses.Add(Course(1, "Course", false, DateTime.UtcNow));
        db.Tags.Add(new Tag { Id = 10, Name = "Tag", Type = TagType.Topic });
        db.CourseTags.Add(new CourseTag { CourseId = 1, TagId = 10 });
        await db.SaveChangesAsync();
        var service = new CourseService(db);

        await Assert.ThrowsAsync<ConflictException>(() => service.AddTagAsync(1, 10));
    }

    [Fact]
    public async Task AddTagAsync_ThrowsWhenCourseOrTagMissing()
    {
        await using var db = CreateDbContext();
        db.Courses.Add(Course(1, "Course", false, DateTime.UtcNow));
        db.Tags.Add(new Tag { Id = 10, Name = "Tag", Type = TagType.Topic });
        await db.SaveChangesAsync();
        var service = new CourseService(db);

        await Assert.ThrowsAsync<NotFoundException>(() => service.AddTagAsync(404, 10));
        await Assert.ThrowsAsync<NotFoundException>(() => service.AddTagAsync(1, 404));
    }

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

    private static Course Course(int id, string name, bool isSynced, DateTime createdAt) => new()
    {
        Id = id,
        Name = name,
        IsSynced = isSynced,
        CreatedAt = createdAt
    };
}
