using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Helpers;
namespace Timeline.Services
{
    public class Avatar
    {
        public string Type { get; set; } = default!;
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "DTO Object")]
        public byte[] Data { get; set; } = default!;
    }
    public class AvatarInfo
    {
        public Avatar Avatar { get; set; } = default!;
        public DateTime LastModified { get; set; }
        public CacheableData ToCacheableData()
        {
            return new CacheableData(Avatar.Type, Avatar.Data, LastModified);
        }
    }
    /// 
    /// Provider for default user avatar.
    /// 
    /// 
    /// Mainly for unit tests.
    /// 
    public interface IDefaultUserAvatarProvider
    {
        /// 
        /// Get the etag of default avatar.
        /// 
        /// 
        Task GetDefaultAvatarETag();
        /// 
        /// Get the default avatar.
        /// 
        Task GetDefaultAvatar();
    }
    public interface IUserAvatarService
    {
        /// 
        /// Get the etag of a user's avatar. Warning: This method does not check the user existence.
        /// 
        /// The id of the user to get avatar etag of.
        /// The etag.
        Task GetAvatarETag(long id);
        /// 
        /// Get avatar of a user. If the user has no avatar set, a default one is returned. Warning: This method does not check the user existence.
        /// 
        /// The id of the user to get avatar of.
        /// The avatar info.
        Task GetAvatar(long id);
        /// 
        /// Set avatar for a user. Warning: This method does not check the user existence.
        /// 
        /// The id of the user to set avatar for.
        /// The avatar. Can be null to delete the saved avatar.
        /// Thrown if any field in  is null when  is not null.
        /// Thrown if avatar is of bad format.
        Task SetAvatar(long id, Avatar? avatar);
    }
    // TODO! : Make this configurable.
    public class DefaultUserAvatarProvider : IDefaultUserAvatarProvider
    {
        private readonly IETagGenerator _eTagGenerator;
        private readonly string _avatarPath;
        private byte[] _cacheData = default!;
        private DateTime _cacheLastModified;
        private string _cacheETag = default!;
        public DefaultUserAvatarProvider(IWebHostEnvironment environment, IETagGenerator eTagGenerator)
        {
            _avatarPath = Path.Combine(environment.ContentRootPath, "default-avatar.png");
            _eTagGenerator = eTagGenerator;
        }
        private async Task CheckAndInit()
        {
            var path = _avatarPath;
            if (_cacheData == null || File.GetLastWriteTime(path) > _cacheLastModified)
            {
                _cacheData = await File.ReadAllBytesAsync(path);
                _cacheLastModified = File.GetLastWriteTime(path);
                _cacheETag = await _eTagGenerator.Generate(_cacheData);
            }
        }
        public async Task GetDefaultAvatarETag()
        {
            await CheckAndInit();
            return _cacheETag;
        }
        public async Task GetDefaultAvatar()
        {
            await CheckAndInit();
            return new AvatarInfo
            {
                Avatar = new Avatar
                {
                    Type = "image/png",
                    Data = _cacheData
                },
                LastModified = _cacheLastModified
            };
        }
    }
    public class UserAvatarService : IUserAvatarService
    {
        private readonly ILogger _logger;
        private readonly DatabaseContext _database;
        private readonly IDefaultUserAvatarProvider _defaultUserAvatarProvider;
        private readonly IImageValidator _imageValidator;
        private readonly IDataManager _dataManager;
        private readonly IClock _clock;
        public UserAvatarService(
            ILogger logger,
            DatabaseContext database,
            IDefaultUserAvatarProvider defaultUserAvatarProvider,
            IImageValidator imageValidator,
            IDataManager dataManager,
            IClock clock)
        {
            _logger = logger;
            _database = database;
            _defaultUserAvatarProvider = defaultUserAvatarProvider;
            _imageValidator = imageValidator;
            _dataManager = dataManager;
            _clock = clock;
        }
        public async Task GetAvatarETag(long id)
        {
            var eTag = (await _database.UserAvatars.Where(a => a.UserId == id).Select(a => new { a.DataTag }).SingleOrDefaultAsync())?.DataTag;
            if (eTag == null)
                return await _defaultUserAvatarProvider.GetDefaultAvatarETag();
            else
                return eTag;
        }
        public async Task GetAvatar(long id)
        {
            var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == id).Select(a => new { a.Type, a.DataTag, a.LastModified }).SingleOrDefaultAsync();
            if (avatarEntity != null)
            {
                if (!LanguageHelper.AreSame(avatarEntity.DataTag == null, avatarEntity.Type == null))
                {
                    var message = Resources.Services.UserAvatarService.ExceptionDatabaseCorruptedDataAndTypeNotSame;
                    _logger.LogCritical(message);
                    throw new DatabaseCorruptedException(message);
                }
                if (avatarEntity.DataTag != null)
                {
                    var data = await _dataManager.GetEntry(avatarEntity.DataTag);
                    return new AvatarInfo
                    {
                        Avatar = new Avatar
                        {
                            Type = avatarEntity.Type!,
                            Data = data
                        },
                        LastModified = avatarEntity.LastModified
                    };
                }
            }
            var defaultAvatar = await _defaultUserAvatarProvider.GetDefaultAvatar();
            if (avatarEntity != null)
                defaultAvatar.LastModified = defaultAvatar.LastModified > avatarEntity.LastModified ? defaultAvatar.LastModified : avatarEntity.LastModified;
            return defaultAvatar;
        }
        public async Task SetAvatar(long id, Avatar? avatar)
        {
            if (avatar != null)
            {
                if (avatar.Data == null)
                    throw new ArgumentException(Resources.Services.UserAvatarService.ExceptionAvatarDataNull, nameof(avatar));
                if (string.IsNullOrEmpty(avatar.Type))
                    throw new ArgumentException(Resources.Services.UserAvatarService.ExceptionAvatarTypeNullOrEmpty, nameof(avatar));
            }
            var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == id).SingleOrDefaultAsync();
            if (avatar == null)
            {
                if (avatarEntity == null || avatarEntity.DataTag == null)
                {
                    return;
                }
                else
                {
                    await _dataManager.FreeEntry(avatarEntity.DataTag);
                    avatarEntity.DataTag = null;
                    avatarEntity.Type = null;
                    avatarEntity.LastModified = _clock.GetCurrentTime();
                    await _database.SaveChangesAsync();
                    _logger.LogInformation(Resources.Services.UserAvatarService.LogUpdateEntity);
                }
            }
            else
            {
                await _imageValidator.Validate(avatar.Data, avatar.Type, true);
                var tag = await _dataManager.RetainEntry(avatar.Data);
                var oldTag = avatarEntity?.DataTag;
                var create = avatarEntity == null;
                if (avatarEntity == null)
                {
                    avatarEntity = new UserAvatarEntity();
                    _database.UserAvatars.Add(avatarEntity);
                }
                avatarEntity.DataTag = tag;
                avatarEntity.Type = avatar.Type;
                avatarEntity.LastModified = _clock.GetCurrentTime();
                avatarEntity.UserId = id;
                await _database.SaveChangesAsync();
                _logger.LogInformation(create ?
                    Resources.Services.UserAvatarService.LogCreateEntity
                    : Resources.Services.UserAvatarService.LogUpdateEntity);
                if (oldTag != null)
                {
                    await _dataManager.FreeEntry(oldTag);
                }
            }
        }
    }
    public static class UserAvatarServiceCollectionExtensions
    {
        public static void AddUserAvatarService(this IServiceCollection services)
        {
            services.AddScoped();
            services.AddScoped();
        }
    }
}