using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Events;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints;
/// 
/// A  responsible for notifying users when libraries are updated.
/// 
public sealed class LibraryChangedNotifier : IHostedService, IDisposable
{
    private readonly ILibraryManager _libraryManager;
    private readonly IServerConfigurationManager _configurationManager;
    private readonly IProviderManager _providerManager;
    private readonly ISessionManager _sessionManager;
    private readonly IUserManager _userManager;
    private readonly ILogger _logger;
    private readonly object _libraryChangedSyncLock = new();
    private readonly List _foldersAddedTo = new();
    private readonly List _foldersRemovedFrom = new();
    private readonly List _itemsAdded = new();
    private readonly List _itemsRemoved = new();
    private readonly List _itemsUpdated = new();
    private readonly ConcurrentDictionary _lastProgressMessageTimes = new();
    private Timer? _libraryUpdateTimer;
    /// 
    /// Initializes a new instance of the  class.
    /// 
    /// The .
    /// The .
    /// The .
    /// The .
    /// The .
    /// The .
    public LibraryChangedNotifier(
        ILibraryManager libraryManager,
        IServerConfigurationManager configurationManager,
        ISessionManager sessionManager,
        IUserManager userManager,
        ILogger logger,
        IProviderManager providerManager)
    {
        _libraryManager = libraryManager;
        _configurationManager = configurationManager;
        _sessionManager = sessionManager;
        _userManager = userManager;
        _logger = logger;
        _providerManager = providerManager;
    }
    /// 
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _libraryManager.ItemAdded += OnLibraryItemAdded;
        _libraryManager.ItemUpdated += OnLibraryItemUpdated;
        _libraryManager.ItemRemoved += OnLibraryItemRemoved;
        _providerManager.RefreshCompleted += OnProviderRefreshCompleted;
        _providerManager.RefreshStarted += OnProviderRefreshStarted;
        _providerManager.RefreshProgress += OnProviderRefreshProgress;
        return Task.CompletedTask;
    }
    /// 
    public Task StopAsync(CancellationToken cancellationToken)
    {
        _libraryManager.ItemAdded -= OnLibraryItemAdded;
        _libraryManager.ItemUpdated -= OnLibraryItemUpdated;
        _libraryManager.ItemRemoved -= OnLibraryItemRemoved;
        _providerManager.RefreshCompleted -= OnProviderRefreshCompleted;
        _providerManager.RefreshStarted -= OnProviderRefreshStarted;
        _providerManager.RefreshProgress -= OnProviderRefreshProgress;
        return Task.CompletedTask;
    }
    private void OnProviderRefreshProgress(object? sender, GenericEventArgs> e)
    {
        var item = e.Argument.Item1;
        if (!EnableRefreshMessage(item))
        {
            return;
        }
        var progress = e.Argument.Item2;
        if (_lastProgressMessageTimes.TryGetValue(item.Id, out var lastMessageSendTime))
        {
            if (progress > 0 && progress < 100 && (DateTime.UtcNow - lastMessageSendTime).TotalMilliseconds < 1000)
            {
                return;
            }
        }
        _lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow);
        var dict = new Dictionary();
        dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
        dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture);
        try
        {
            _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None);
        }
        catch
        {
        }
        var collectionFolders = _libraryManager.GetCollectionFolders(item);
        foreach (var collectionFolder in collectionFolders)
        {
            var collectionFolderDict = new Dictionary
            {
                ["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture),
                ["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture)
            };
            try
            {
                _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None);
            }
            catch
            {
            }
        }
    }
    private void OnProviderRefreshStarted(object? sender, GenericEventArgs e)
        => OnProviderRefreshProgress(sender, new GenericEventArgs>(new Tuple(e.Argument, 0)));
    private void OnProviderRefreshCompleted(object? sender, GenericEventArgs e)
    {
        OnProviderRefreshProgress(sender, new GenericEventArgs>(new Tuple(e.Argument, 100)));
        _lastProgressMessageTimes.TryRemove(e.Argument.Id, out _);
    }
    private static bool EnableRefreshMessage(BaseItem item)
        => item is Folder { IsRoot: false, IsTopParent: true }
            and not (AggregateFolder or UserRootFolder or UserView or Channel);
    private void OnLibraryItemAdded(object? sender, ItemChangeEventArgs e)
        => OnLibraryChange(e.Item, e.Parent, _itemsAdded, _foldersAddedTo);
    private void OnLibraryItemUpdated(object? sender, ItemChangeEventArgs e)
        => OnLibraryChange(e.Item, e.Parent, _itemsUpdated, null);
    private void OnLibraryItemRemoved(object? sender, ItemChangeEventArgs e)
        => OnLibraryChange(e.Item, e.Parent, _itemsRemoved, _foldersRemovedFrom);
    private void OnLibraryChange(BaseItem item, BaseItem parent, List itemsList, List? foldersList)
    {
        if (!FilterItem(item))
        {
            return;
        }
        lock (_libraryChangedSyncLock)
        {
            var updateDuration = TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration);
            if (_libraryUpdateTimer is null)
            {
                _libraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, updateDuration, Timeout.InfiniteTimeSpan);
            }
            else
            {
                _libraryUpdateTimer.Change(updateDuration, Timeout.InfiniteTimeSpan);
            }
            if (foldersList is not null && parent is Folder folder)
            {
                foldersList.Add(folder);
            }
            itemsList.Add(item);
        }
    }
    private async void LibraryUpdateTimerCallback(object? state)
    {
        List foldersAddedTo;
        List foldersRemovedFrom;
        List itemsUpdated;
        List itemsAdded;
        List itemsRemoved;
        lock (_libraryChangedSyncLock)
        {
            // Remove dupes in case some were saved multiple times
            foldersAddedTo = _foldersAddedTo
                .DistinctBy(x => x.Id)
                .ToList();
            foldersRemovedFrom = _foldersRemovedFrom
                .DistinctBy(x => x.Id)
                .ToList();
            itemsUpdated = _itemsUpdated
                .Where(i => !_itemsAdded.Contains(i))
                .DistinctBy(x => x.Id)
                .ToList();
            itemsAdded = _itemsAdded.ToList();
            itemsRemoved = _itemsRemoved.ToList();
            if (_libraryUpdateTimer is not null)
            {
                _libraryUpdateTimer.Dispose();
                _libraryUpdateTimer = null;
            }
            _itemsAdded.Clear();
            _itemsRemoved.Clear();
            _itemsUpdated.Clear();
            _foldersAddedTo.Clear();
            _foldersRemovedFrom.Clear();
        }
        await SendChangeNotifications(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, CancellationToken.None).ConfigureAwait(false);
    }
    private async Task SendChangeNotifications(
        List itemsAdded,
        List itemsUpdated,
        List itemsRemoved,
        List foldersAddedTo,
        List foldersRemovedFrom,
        CancellationToken cancellationToken)
    {
        var userIds = _sessionManager.Sessions
            .Select(i => i.UserId)
            .Where(i => !i.IsEmpty())
            .Distinct()
            .ToArray();
        foreach (var userId in userIds)
        {
            LibraryUpdateInfo info;
            try
            {
                info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, userId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in GetLibraryUpdateInfo");
                return;
            }
            if (info.IsEmpty)
            {
                continue;
            }
            try
            {
                await _sessionManager.SendMessageToUserSessions(
                        new List { userId },
                        SessionMessageType.LibraryChanged,
                        info,
                        cancellationToken)
                    .ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error sending LibraryChanged message");
            }
        }
    }
    private LibraryUpdateInfo GetLibraryUpdateInfo(
        List itemsAdded,
        List itemsUpdated,
        List itemsRemoved,
        List foldersAddedTo,
        List foldersRemovedFrom,
        Guid userId)
    {
        var user = _userManager.GetUserById(userId);
        ArgumentNullException.ThrowIfNull(user);
        var newAndRemoved = new List();
        newAndRemoved.AddRange(foldersAddedTo);
        newAndRemoved.AddRange(foldersRemovedFrom);
        var allUserRootChildren = _libraryManager.GetUserRootFolder()
            .GetChildren(user, true)
            .OfType()
            .ToList();
        return new LibraryUpdateInfo
        {
            ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user))
                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
                .Distinct()
                .ToArray(),
            ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user))
                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
                .Distinct()
                .ToArray(),
            ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true))
                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
                .Distinct()
                .ToArray(),
            FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user))
                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
                .Distinct()
                .ToArray(),
            FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user))
                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
                .Distinct()
                .ToArray(),
            CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray()
        };
    }
    private static bool FilterItem(BaseItem item)
    {
        if (!item.IsFolder && !item.HasPathProtocol)
        {
            return false;
        }
        if (item is IItemByName && item is not MusicArtist)
        {
            return false;
        }
        return item.SourceType == SourceType.Library;
    }
    private static IEnumerable GetTopParentIds(List items, List allUserRootChildren)
    {
        var list = new List();
        foreach (var item in items)
        {
            // If the physical root changed, return the user root
            if (item is AggregateFolder)
            {
                continue;
            }
            foreach (var folder in allUserRootChildren)
            {
                list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
            }
        }
        return list.Distinct(StringComparer.Ordinal);
    }
    private T[] TranslatePhysicalItemToUserLibrary(T item, User user, bool includeIfNotFound = false)
        where T : BaseItem
    {
        // If the physical root changed, return the user root
        if (item is AggregateFolder)
        {
            return _libraryManager.GetUserRootFolder() is T t ? new[] { t } : Array.Empty();
        }
        // Return it only if it's in the user's library
        if (includeIfNotFound || item.IsVisibleStandalone(user))
        {
            return new[] { item };
        }
        return Array.Empty();
    }
    /// 
    public void Dispose()
    {
        _libraryUpdateTimer?.Dispose();
        _libraryUpdateTimer = null;
    }
}