using Kavita.Database.Tests;
using Kavita.Models.Builders;
using Kavita.Models.Entities.Enums;
using Kavita.Services.Builders;
using Kavita.Services.Helpers;
using Xunit.Abstractions;

namespace Kavita.Services.Tests.Helpers;

public class PersonHelperTests(ITestOutputHelper outputHelper): AbstractDbTest(outputHelper)
{

    // 1. Test adding new people and keeping existing ones
    [Fact]
    public async Task UpdateChapterPeopleAsync_AddNewPeople_ExistingPersonRetained()
    {
        var (unitOfWork, context, mapper) = await CreateDatabase();

        var library = new LibraryBuilder("My Library")
            .Build();

        unitOfWork.LibraryRepository.Add(library);
        await unitOfWork.CommitAsync();

        var existingPerson = new PersonBuilder("Joe Shmo").Build();
        var chapter = new ChapterBuilder("1").Build();

        // Create an existing person and assign them to the series with a role
        var series = new SeriesBuilder("Test 1")
            .WithLibraryId(library.Id)
            .WithFormat(MangaFormat.Archive)
            .WithMetadata(new SeriesMetadataBuilder()
                .WithPerson(existingPerson, PersonRole.Editor)
                .Build())
            .WithVolume(new VolumeBuilder("1").WithChapter(chapter).Build())
            .Build();

        unitOfWork.SeriesRepository.Add(series);
        await unitOfWork.CommitAsync();

        // Call UpdateChapterPeopleAsync with one existing and one new person
        await PersonHelper.UpdateChapterPeopleAsync(chapter, new List<string> { "Joe Shmo", "New Person" }, PersonRole.Editor, unitOfWork);

        // Assert existing person retained and new person added
        var people = await unitOfWork.PersonRepository.GetAllPeople();
        Assert.Contains(people, p => p.Name == "Joe Shmo");
        Assert.Contains(people, p => p.Name == "New Person");

        var chapterPeople = chapter.People.Select(cp => cp.Person.Name).ToList();
        Assert.Contains("Joe Shmo", chapterPeople);
        Assert.Contains("New Person", chapterPeople);
    }

    // 2. Test removing a person no longer in the list
    [Fact]
    public async Task UpdateChapterPeopleAsync_RemovePeople()
    {
        var (unitOfWork, context, mapper) = await CreateDatabase();

        var library = new LibraryBuilder("My Library")
            .Build();

        unitOfWork.LibraryRepository.Add(library);
        await unitOfWork.CommitAsync();

        var existingPerson1 = new PersonBuilder("Joe Shmo").Build();
        var existingPerson2 = new PersonBuilder("Jane Doe").Build();
        var chapter = new ChapterBuilder("1")
            .WithPerson(existingPerson1, PersonRole.Editor)
            .WithPerson(existingPerson2, PersonRole.Editor)
            .Build();

        var series = new SeriesBuilder("Test 1")
            .WithLibraryId(library.Id)
            .WithVolume(new VolumeBuilder("1")
                .WithChapter(chapter)
                .Build())
            .Build();

        unitOfWork.SeriesRepository.Add(series);
        await unitOfWork.CommitAsync();

        // Call UpdateChapterPeopleAsync with only one person
        await PersonHelper.UpdateChapterPeopleAsync(chapter, new List<string> { "Joe Shmo" }, PersonRole.Editor, unitOfWork);

        // PersonHelper does not remove the Person from the global DbSet itself
        await unitOfWork.PersonRepository.RemoveAllPeopleNoLongerAssociated();

        var people = await unitOfWork.PersonRepository.GetAllPeople();
        Assert.DoesNotContain(people, p => p.Name == "Jane Doe");

        var chapterPeople = chapter.People.Select(cp => cp.Person.Name).ToList();
        Assert.Contains("Joe Shmo", chapterPeople);
        Assert.DoesNotContain("Jane Doe", chapterPeople);
    }

    // 3. Test no changes when the list of people is the same
    [Fact]
    public async Task UpdateChapterPeopleAsync_NoChanges()
    {
        var (unitOfWork, context, mapper) = await CreateDatabase();

        var library = new LibraryBuilder("My Library")
            .Build();

        unitOfWork.LibraryRepository.Add(library);
        await unitOfWork.CommitAsync();

        var existingPerson = new PersonBuilder("Joe Shmo").Build();
        var chapter = new ChapterBuilder("1").WithPerson(existingPerson, PersonRole.Editor).Build();

        var series = new SeriesBuilder("Test 1")
            .WithLibraryId(library.Id)
            .WithVolume(new VolumeBuilder("1")
                .WithChapter(chapter)
                .Build())
            .Build();

        unitOfWork.SeriesRepository.Add(series);
        await unitOfWork.CommitAsync();

        // Call UpdateChapterPeopleAsync with the same list
        await PersonHelper.UpdateChapterPeopleAsync(chapter, new List<string> { "Joe Shmo" }, PersonRole.Editor, unitOfWork);

        var people = await unitOfWork.PersonRepository.GetAllPeople();
        Assert.Contains(people, p => p.Name == "Joe Shmo");

        var chapterPeople = chapter.People.Select(cp => cp.Person.Name).ToList();
        Assert.Contains("Joe Shmo", chapterPeople);
        Assert.Single(chapter.People); // No duplicate entries
    }

    // 4. Test multiple roles for a person
    [Fact]
    public async Task UpdateChapterPeopleAsync_MultipleRoles()
    {
        var (unitOfWork, context, mapper) = await CreateDatabase();

        var library = new LibraryBuilder("My Library")
            .Build();

        unitOfWork.LibraryRepository.Add(library);
        await unitOfWork.CommitAsync();

        var person = new PersonBuilder("Joe Shmo").Build();
        var chapter = new ChapterBuilder("1").WithPerson(person, PersonRole.Writer).Build();

        var series = new SeriesBuilder("Test 1")
            .WithLibraryId(library.Id)
            .WithVolume(new VolumeBuilder("1")
                .WithChapter(chapter)
                .Build())
            .Build();

        unitOfWork.SeriesRepository.Add(series);
        await unitOfWork.CommitAsync();

        // Add same person as Editor
        await PersonHelper.UpdateChapterPeopleAsync(chapter, new List<string> { "Joe Shmo" }, PersonRole.Editor, unitOfWork);

        // Ensure that the same person is assigned with two roles
        var chapterPeople = chapter
            .People
            .Where(cp =>
                cp.Person.Name == "Joe Shmo")
            .ToList();
        Assert.Equal(2, chapterPeople.Count); // One for each role
        Assert.Contains(chapterPeople, cp => cp.Role == PersonRole.Writer);
        Assert.Contains(chapterPeople, cp => cp.Role == PersonRole.Editor);
    }

    [Fact]
    public async Task UpdateChapterPeopleAsync_MatchOnAlias_NoChanges()
    {
        var (unitOfWork, context, mapper) = await CreateDatabase();

        var library = new LibraryBuilder("My Library")
            .Build();

        unitOfWork.LibraryRepository.Add(library);
        await unitOfWork.CommitAsync();

        var person = new PersonBuilder("Joe Doe")
            .WithAlias("Jonny Doe")
            .Build();

        var chapter = new ChapterBuilder("1")
            .WithPerson(person, PersonRole.Editor)
            .Build();

        var series = new SeriesBuilder("Test 1")
            .WithLibraryId(library.Id)
            .WithVolume(new VolumeBuilder("1")
                .WithChapter(chapter)
                .Build())
            .Build();

        unitOfWork.SeriesRepository.Add(series);
        await unitOfWork.CommitAsync();

        // Add on Name
        await PersonHelper.UpdateChapterPeopleAsync(chapter, new List<string> { "Joe Doe" }, PersonRole.Editor, unitOfWork);
        await unitOfWork.CommitAsync();

        var allPeople = await unitOfWork.PersonRepository.GetAllPeople();
        Assert.Single(allPeople);

        // Add on alias
        await PersonHelper.UpdateChapterPeopleAsync(chapter, new List<string> { "Jonny Doe" }, PersonRole.Editor, unitOfWork);
        await unitOfWork.CommitAsync();

        allPeople = await unitOfWork.PersonRepository.GetAllPeople();
        Assert.Single(allPeople);
    }

    // TODO: Unit tests for series
}
