using ErsatzTV.Application.MediaSources;
using ErsatzTV.Core.Api.MediaSources;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Tests.Support;
using Microsoft.EntityFrameworkCore;
using NUnit.Framework;
using Shouldly;

namespace ErsatzTV.Tests.Application.MediaSources;

[TestFixture]
public class GetAllMediaSourcesForApiHandlerTests
{
    private InMemoryTvContext _db = null!;

    [SetUp]
    public async Task SetUp() => _db = await InMemoryTvContext.CreateAsync();

    [TearDown]
    public async Task TearDown() => await _db.DisposeAsync();

    [Test]
    public async Task Handle_Should_Group_Configured_Libraries_By_Source_With_Item_Counts()
    {
        await SeedMediaSources();
        var handler = new GetAllMediaSourcesForApiHandler(_db.Factory);

        var result = await handler.Handle(new GetAllMediaSourcesForApi(), CancellationToken.None);

        result.Count.ShouldBe(4);

        result[0].Kind.ShouldBe("Local");
        result[0].Name.ShouldBe("Local");
        result[0].ConnectionAddress.ShouldBeNull();
        result[0].Libraries.Single().Name.ShouldBe("Local Movies");
        result[0].Libraries.Single().LastScan.ShouldBe(new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc));
        result[0].Libraries.Single().ItemCount.ShouldBe(2);

        result[1].Kind.ShouldBe("Emby");
        result[1].Name.ShouldBe("Emby Server");
        result[1].ConnectionAddress.ShouldBe("http://emby.local");
        result[1].Libraries.Single().Name.ShouldBe("Emby Shows");
        result[1].Libraries.Single().ItemCount.ShouldBe(1);

        result[2].Kind.ShouldBe("Jellyfin");
        result[2].Name.ShouldBe("Jellyfin Server");
        result[2].ConnectionAddress.ShouldBe("http://jellyfin.local");
        result[2].Libraries.ShouldBeEmpty();

        result[3].Kind.ShouldBe("Plex");
        result[3].Name.ShouldBe("Plex Server");
        result[3].ConnectionAddress.ShouldBe("http://plex.local");
        result[3].Libraries.Single().Name.ShouldBe("Plex Movies");
        result[3].Libraries.Single().ItemCount.ShouldBe(0);
    }

    [Test]
    public async Task Handle_Should_Succeed_On_Empty_Database()
    {
        // regression: the Dapper item-count query 500'd on a fresh database because
        // SQLite reports COUNT(*) as BLOB when there are no rows to infer from
        var handler = new GetAllMediaSourcesForApiHandler(_db.Factory);

        var result = await handler.Handle(new GetAllMediaSourcesForApi(), CancellationToken.None);

        result.ShouldBeEmpty();
    }

    [Test]
    public async Task Handle_Should_Report_Sentinel_And_Null_LastScan_As_Null()
    {
        await using (TvContext context = _db.CreateContext())
        {
            var source = new LocalMediaSource
            {
                Libraries =
                [
                    new LocalLibrary
                    {
                        Name = "Never Scanned Sentinel",
                        MediaKind = LibraryMediaKind.Movies,
                        LastScan = new DateTime(1, 1, 1),
                        Paths = [MakePath("/media/sentinel", 0)]
                    },
                    new LocalLibrary
                    {
                        Name = "Never Scanned Null",
                        MediaKind = LibraryMediaKind.Movies,
                        LastScan = null,
                        Paths = [MakePath("/media/nullscan", 0)]
                    },
                    new LocalLibrary
                    {
                        Name = "Really Scanned",
                        MediaKind = LibraryMediaKind.Movies,
                        LastScan = new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc),
                        Paths = [MakePath("/media/scanned", 0)]
                    }
                ]
            };

            context.MediaSources.Add(source);
            await context.SaveChangesAsync();
        }

        var handler = new GetAllMediaSourcesForApiHandler(_db.Factory);
        var result = await handler.Handle(new GetAllMediaSourcesForApi(), CancellationToken.None);

        MediaSourceLibraryResponseModel[] libraries = result.Single().Libraries.ToArray();

        libraries.Single(l => l.Name == "Never Scanned Sentinel").LastScan.ShouldBeNull();
        libraries.Single(l => l.Name == "Never Scanned Null").LastScan.ShouldBeNull();
        libraries.Single(l => l.Name == "Really Scanned").LastScan
            .ShouldBe(new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc));
    }

    [Test]
    public async Task Handle_Should_Exclude_Unconfigured_And_Not_Synced_Libraries()
    {
        await SeedMediaSources();
        var handler = new GetAllMediaSourcesForApiHandler(_db.Factory);

        var result = await handler.Handle(new GetAllMediaSourcesForApi(), CancellationToken.None);

        result.SelectMany(s => s.Libraries).Select(l => l.Name)
            .ShouldNotContain("Empty Local");
        result.SelectMany(s => s.Libraries).Select(l => l.Name)
            .ShouldNotContain("Disabled Jellyfin");
    }

    private async Task SeedMediaSources()
    {
        await using TvContext context = _db.CreateContext();

        var localSource = new LocalMediaSource
        {
            Libraries =
            [
                new LocalLibrary
                {
                    Name = "Local Movies",
                    MediaKind = LibraryMediaKind.Movies,
                    LastScan = new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc),
                    Paths = [MakePath("/media/movies", 2)]
                },
                new LocalLibrary
                {
                    Name = "Empty Local",
                    MediaKind = LibraryMediaKind.Shows,
                    Paths = []
                }
            ]
        };

        var plexSource = new PlexMediaSource
        {
            ServerName = "Plex Server",
            ProductVersion = "1",
            Platform = "Linux",
            PlatformVersion = "1",
            ClientIdentifier = "plex",
            Connections = [new PlexConnection { IsActive = true, Uri = "http://plex.local" }],
            PathReplacements = [],
            Libraries =
            [
                new PlexLibrary
                {
                    Name = "Plex Movies",
                    MediaKind = LibraryMediaKind.Movies,
                    Key = "1",
                    ShouldSyncItems = true,
                    Paths = []
                }
            ]
        };

        var embySource = new EmbyMediaSource
        {
            ServerName = "Emby Server",
            OperatingSystem = "Linux",
            Connections = [new EmbyConnection { Address = "http://emby.local" }],
            PathReplacements = [],
            Libraries =
            [
                new EmbyLibrary
                {
                    Name = "Emby Shows",
                    MediaKind = LibraryMediaKind.Shows,
                    ItemId = "emby-shows",
                    ShouldSyncItems = true,
                    PathInfos = [],
                    Paths = [MakePath("/emby/shows", 1)]
                }
            ]
        };

        var jellyfinSource = new JellyfinMediaSource
        {
            ServerName = "Jellyfin Server",
            OperatingSystem = "Linux",
            Connections = [new JellyfinConnection { Address = "http://jellyfin.local" }],
            PathReplacements = [],
            Libraries =
            [
                new JellyfinLibrary
                {
                    Name = "Disabled Jellyfin",
                    MediaKind = LibraryMediaKind.Movies,
                    ItemId = "jellyfin-movies",
                    ShouldSyncItems = false,
                    PathInfos = [],
                    Paths = [MakePath("/jellyfin/movies", 1)]
                }
            ]
        };

        context.MediaSources.AddRange(localSource, plexSource, embySource, jellyfinSource);
        await context.SaveChangesAsync();
        context.ChangeTracker.Clear();
    }

    private static LibraryPath MakePath(string path, int mediaItemCount) =>
        new()
        {
            Path = path,
            LibraryFolders = [],
            MediaItems = Enumerable.Range(0, mediaItemCount)
                .Select(_ => new Movie
                {
                    MovieMetadata = [],
                    MediaVersions = [],
                    Collections = [],
                    CollectionItems = [],
                    TraktListItems = []
                })
                .Cast<MediaItem>()
                .ToList()
        };
}
