using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Hosting;
namespace Emby.Server.Implementations.EntryPoints
{
    /// 
    ///  responsible for notifying users when associated item data is updated.
    /// 
    public sealed class UserDataChangeNotifier : IHostedService, IDisposable
    {
        private const int UpdateDuration = 500;
        private readonly ISessionManager _sessionManager;
        private readonly IUserDataManager _userDataManager;
        private readonly IUserManager _userManager;
        private readonly Dictionary> _changedItems = new();
        private readonly object _syncLock = new();
        private Timer? _updateTimer;
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The .
        /// The .
        /// The .
        public UserDataChangeNotifier(
            IUserDataManager userDataManager,
            ISessionManager sessionManager,
            IUserManager userManager)
        {
            _userDataManager = userDataManager;
            _sessionManager = sessionManager;
            _userManager = userManager;
        }
        /// 
        public Task StartAsync(CancellationToken cancellationToken)
        {
            _userDataManager.UserDataSaved += OnUserDataManagerUserDataSaved;
            return Task.CompletedTask;
        }
        /// 
        public Task StopAsync(CancellationToken cancellationToken)
        {
            _userDataManager.UserDataSaved -= OnUserDataManagerUserDataSaved;
            return Task.CompletedTask;
        }
        private void OnUserDataManagerUserDataSaved(object? sender, UserDataSaveEventArgs e)
        {
            if (e.SaveReason == UserDataSaveReason.PlaybackProgress)
            {
                return;
            }
            lock (_syncLock)
            {
                if (_updateTimer is null)
                {
                    _updateTimer = new Timer(
                        UpdateTimerCallback,
                        null,
                        UpdateDuration,
                        Timeout.Infinite);
                }
                else
                {
                    _updateTimer.Change(UpdateDuration, Timeout.Infinite);
                }
                if (!_changedItems.TryGetValue(e.UserId, out List? keys))
                {
                    keys = new List();
                    _changedItems[e.UserId] = keys;
                }
                keys.Add(e.Item);
                var baseItem = e.Item;
                // Go up one level for indicators
                if (baseItem is not null)
                {
                    var parent = baseItem.GetOwner() ?? baseItem.GetParent();
                    if (parent is not null)
                    {
                        keys.Add(parent);
                    }
                }
            }
        }
        private async void UpdateTimerCallback(object? state)
        {
            List>> changes;
            lock (_syncLock)
            {
                // Remove dupes in case some were saved multiple times
                changes = _changedItems.ToList();
                _changedItems.Clear();
                if (_updateTimer is not null)
                {
                    _updateTimer.Dispose();
                    _updateTimer = null;
                }
            }
            foreach (var (userId, changedItems) in changes)
            {
                await _sessionManager.SendMessageToUserSessions(
                    [userId],
                    SessionMessageType.UserDataChanged,
                    () => GetUserDataChangeInfo(userId, changedItems),
                    default).ConfigureAwait(false);
            }
        }
        private UserDataChangeInfo GetUserDataChangeInfo(Guid userId, List changedItems)
        {
            var user = _userManager.GetUserById(userId);
            return new UserDataChangeInfo
            {
                UserId = userId.ToString("N", CultureInfo.InvariantCulture),
                UserDataList = changedItems
                    .DistinctBy(x => x.Id)
                    .Select(i =>
                    {
                        var dto = _userDataManager.GetUserDataDto(i, user);
                        dto.ItemId = i.Id.ToString("N", CultureInfo.InvariantCulture);
                        return dto;
                    })
                    .ToArray()
            };
        }
        /// 
        public void Dispose()
        {
            _updateTimer?.Dispose();
            _updateTimer = null;
        }
    }
}