// <copyright file="BaseItemRepositoryTests.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>

namespace Jellyfin.Server.Implementations.Tests.Item;

using System;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Server.Implementations.Item;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Persistence;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;

public class BaseItemRepositoryTests
{
    [Fact]
    public void DeserializeBaseItem_WithUnknownType_ReturnsNull()
    {
        // Arrange
        var entity = new BaseItemEntity
        {
            Id = Guid.NewGuid(),
            Type = "NonExistent.Plugin.CustomItemType"
        };

        // Act
        var result = BaseItemRepository.DeserializeBaseItem(entity, NullLogger.Instance, null, false);

        // Assert
        Assert.Null(result);
    }

    [Fact]
    public void DeserializeBaseItem_WithUnknownType_LogsWarning()
    {
        // Arrange
        var entity = new BaseItemEntity
        {
            Id = Guid.NewGuid(),
            Type = "NonExistent.Plugin.CustomItemType"
        };
        var loggerMock = new Mock<ILogger>();

        // Act
        BaseItemRepository.DeserializeBaseItem(entity, loggerMock.Object, null, false);

        // Assert
        loggerMock.Verify(
            x => x.Log(
                LogLevel.Warning,
                It.IsAny<EventId>(),
                It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("unknown type", StringComparison.OrdinalIgnoreCase)),
                It.IsAny<Exception?>(),
                It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
            Times.Once);
    }

    [Fact]
    public void DeserializeBaseItem_WithKnownType_ReturnsItem()
    {
        // Arrange
        var entity = new BaseItemEntity
        {
            Id = Guid.NewGuid(),
            Type = "MediaBrowser.Controller.Entities.Movies.Movie"
        };

        // Act
        var result = BaseItemRepository.DeserializeBaseItem(entity, NullLogger.Instance, null, false);

        // Assert
        Assert.NotNull(result);
    }

    [Fact]
    public async Task DeleteItemAsync_RecreatesMissingPlaceholderBeforeDetachingUserData()
    {
        await using var dbFactory = new TestDbContextFactory();

        Guid itemId;
        await using (var context = await dbFactory.CreateDbContextAsync(CancellationToken.None))
        {
            await context.Database.EnsureCreatedAsync();

            var user = new User("test-user", "Default", "Default");
            var item = new BaseItemEntity
            {
                Id = Guid.NewGuid(),
                Type = "TestItem",
                Name = "Delete me"
            };

            context.Users.Add(user);
            context.BaseItems.Add(item);
            context.UserData.Add(new UserData
            {
                CustomDataKey = "userdata-key",
                ItemId = item.Id,
                Item = item,
                UserId = user.Id,
                User = user,
                Played = true,
                PlayCount = 1
            });

            await context.SaveChangesAsync();

            var placeholder = await context.BaseItems.SingleAsync(e => e.Id.Equals(BaseItemRepository.PlaceholderId));
            context.BaseItems.Remove(placeholder);
            await context.SaveChangesAsync();

            itemId = item.Id;
        }

        var repository = new BaseItemRepository(
            dbFactory,
            Mock.Of<IServerApplicationHost>(),
            Mock.Of<IItemTypeLookup>(),
            Mock.Of<IServerConfigurationManager>(),
            NullLogger<BaseItemRepository>.Instance);

        await repository.DeleteItemAsync([itemId], CancellationToken.None);

        await using var verificationContext = await dbFactory.CreateDbContextAsync(CancellationToken.None);
        var recreatedPlaceholder = await verificationContext.BaseItems.SingleOrDefaultAsync(e => e.Id.Equals(BaseItemRepository.PlaceholderId));
        var detachedUserData = await verificationContext.UserData.SingleAsync();

        Assert.NotNull(recreatedPlaceholder);
        Assert.Null(await verificationContext.BaseItems.SingleOrDefaultAsync(e => e.Id.Equals(itemId)));
        Assert.Equal(BaseItemRepository.PlaceholderId, detachedUserData.ItemId);
        Assert.NotNull(detachedUserData.RetentionDate);
    }

    private sealed class TestDbContextFactory : IDbContextFactory<JellyfinDbContext>, IAsyncDisposable
    {
        private readonly SqliteConnection _connection;
        private readonly DbContextOptions<JellyfinDbContext> _options;
        private readonly Mock<IJellyfinDatabaseProvider> _databaseProviderMock;
        private readonly Mock<IEntityFrameworkCoreLockingBehavior> _lockingBehaviorMock;

        public TestDbContextFactory()
        {
            _connection = new SqliteConnection("Data Source=:memory:");
            _connection.Open();

            _options = new DbContextOptionsBuilder<JellyfinDbContext>()
                .UseSqlite(_connection)
                .Options;

            _databaseProviderMock = new Mock<IJellyfinDatabaseProvider>();
            _databaseProviderMock.Setup(m => m.OnModelCreating(It.IsAny<ModelBuilder>()));
            _databaseProviderMock.Setup(m => m.ConfigureConventions(It.IsAny<ModelConfigurationBuilder>()));

            _lockingBehaviorMock = new Mock<IEntityFrameworkCoreLockingBehavior>();
            _lockingBehaviorMock
                .Setup(m => m.OnSaveChanges(It.IsAny<JellyfinDbContext>(), It.IsAny<Action>()))
                .Callback<JellyfinDbContext, Action>((_, saveChanges) => saveChanges());
            _lockingBehaviorMock
                .Setup(m => m.OnSaveChangesAsync(It.IsAny<JellyfinDbContext>(), It.IsAny<Func<Task>>()))
                .Returns<JellyfinDbContext, Func<Task>>((_, saveChanges) => saveChanges());
        }

        public JellyfinDbContext CreateDbContext()
        {
            return new JellyfinDbContext(
                _options,
                NullLogger<JellyfinDbContext>.Instance,
                _databaseProviderMock.Object,
                _lockingBehaviorMock.Object);
        }

        public ValueTask<JellyfinDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default)
        {
            return ValueTask.FromResult(CreateDbContext());
        }

        public async ValueTask DisposeAsync()
        {
            await _connection.DisposeAsync();
        }
    }
}
