#pragma warning disable CA2227
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Interfaces;
namespace Jellyfin.Data.Entities
{
    /// 
    /// An entity representing a user.
    /// 
    public class User : IHasPermissions, IHasConcurrencyToken
    {
        /// 
        /// The values being delimited here are Guids, so commas work as they do not appear in Guids.
        /// 
        private const char Delimiter = ',';
        /// 
        /// Initializes a new instance of the  class.
        /// Public constructor with required data.
        /// 
        /// The username for the new user.
        /// The Id of the user's authentication provider.
        /// The Id of the user's password reset provider.
        public User(string username, string authenticationProviderId, string passwordResetProviderId)
        {
            if (string.IsNullOrEmpty(username))
            {
                throw new ArgumentNullException(nameof(username));
            }
            if (string.IsNullOrEmpty(authenticationProviderId))
            {
                throw new ArgumentNullException(nameof(authenticationProviderId));
            }
            if (string.IsNullOrEmpty(passwordResetProviderId))
            {
                throw new ArgumentNullException(nameof(passwordResetProviderId));
            }
            Username = username;
            AuthenticationProviderId = authenticationProviderId;
            PasswordResetProviderId = passwordResetProviderId;
            AccessSchedules = new HashSet();
            ItemDisplayPreferences = new HashSet();
            // Groups = new HashSet();
            Permissions = new HashSet();
            Preferences = new HashSet();
            // ProviderMappings = new HashSet();
            // Set default values
            Id = Guid.NewGuid();
            InvalidLoginAttemptCount = 0;
            EnableUserPreferenceAccess = true;
            MustUpdatePassword = false;
            DisplayMissingEpisodes = false;
            DisplayCollectionsView = false;
            HidePlayedInLatest = true;
            RememberAudioSelections = true;
            RememberSubtitleSelections = true;
            EnableNextEpisodeAutoPlay = true;
            EnableAutoLogin = false;
            PlayDefaultAudioTrack = true;
            SubtitleMode = SubtitlePlaybackMode.Default;
            SyncPlayAccess = SyncPlayAccess.CreateAndJoinGroups;
            AddDefaultPermissions();
            AddDefaultPreferences();
        }
        /// 
        /// Initializes a new instance of the  class.
        /// Default constructor. Protected due to required properties, but present because EF needs it.
        /// 
        protected User()
        {
        }
        /// 
        /// Gets or sets the Id of the user.
        /// 
        /// 
        /// Identity, Indexed, Required.
        /// 
        [JsonIgnore]
        public Guid Id { get; set; }
        /// 
        /// Gets or sets the user's name.
        /// 
        /// 
        /// Required, Max length = 255.
        /// 
        [Required]
        [MaxLength(255)]
        [StringLength(255)]
        public string Username { get; set; }
        /// 
        /// Gets or sets the user's password, or null if none is set.
        /// 
        /// 
        /// Max length = 65535.
        /// 
        [MaxLength(65535)]
        [StringLength(65535)]
        public string Password { get; set; }
        /// 
        /// Gets or sets the user's easy password, or null if none is set.
        /// 
        /// 
        /// Max length = 65535.
        /// 
        [MaxLength(65535)]
        [StringLength(65535)]
        public string EasyPassword { get; set; }
        /// 
        /// Gets or sets a value indicating whether the user must update their password.
        /// 
        /// 
        /// Required.
        /// 
        public bool MustUpdatePassword { get; set; }
        /// 
        /// Gets or sets the audio language preference.
        /// 
        /// 
        /// Max length = 255.
        /// 
        [MaxLength(255)]
        [StringLength(255)]
        public string AudioLanguagePreference { get; set; }
        /// 
        /// Gets or sets the authentication provider id.
        /// 
        /// 
        /// Required, Max length = 255.
        /// 
        [Required]
        [MaxLength(255)]
        [StringLength(255)]
        public string AuthenticationProviderId { get; set; }
        /// 
        /// Gets or sets the password reset provider id.
        /// 
        /// 
        /// Required, Max length = 255.
        /// 
        [Required]
        [MaxLength(255)]
        [StringLength(255)]
        public string PasswordResetProviderId { get; set; }
        /// 
        /// Gets or sets the invalid login attempt count.
        /// 
        /// 
        /// Required.
        /// 
        public int InvalidLoginAttemptCount { get; set; }
        /// 
        /// Gets or sets the last activity date.
        /// 
        public DateTime? LastActivityDate { get; set; }
        /// 
        /// Gets or sets the last login date.
        /// 
        public DateTime? LastLoginDate { get; set; }
        /// 
        /// Gets or sets the number of login attempts the user can make before they are locked out.
        /// 
        public int? LoginAttemptsBeforeLockout { get; set; }
        /// 
        /// Gets or sets the subtitle mode.
        /// 
        /// 
        /// Required.
        /// 
        public SubtitlePlaybackMode SubtitleMode { get; set; }
        /// 
        /// Gets or sets a value indicating whether the default audio track should be played.
        /// 
        /// 
        /// Required.
        /// 
        public bool PlayDefaultAudioTrack { get; set; }
        /// 
        /// Gets or sets the subtitle language preference.
        /// 
        /// 
        /// Max length = 255.
        /// 
        [MaxLength(255)]
        [StringLength(255)]
        public string SubtitleLanguagePreference { get; set; }
        /// 
        /// Gets or sets a value indicating whether missing episodes should be displayed.
        /// 
        /// 
        /// Required.
        /// 
        public bool DisplayMissingEpisodes { get; set; }
        /// 
        /// Gets or sets a value indicating whether to display the collections view.
        /// 
        /// 
        /// Required.
        /// 
        public bool DisplayCollectionsView { get; set; }
        /// 
        /// Gets or sets a value indicating whether the user has a local password.
        /// 
        /// 
        /// Required.
        /// 
        public bool EnableLocalPassword { get; set; }
        /// 
        /// Gets or sets a value indicating whether the server should hide played content in "Latest".
        /// 
        /// 
        /// Required.
        /// 
        public bool HidePlayedInLatest { get; set; }
        /// 
        /// Gets or sets a value indicating whether to remember audio selections on played content.
        /// 
        /// 
        /// Required.
        /// 
        public bool RememberAudioSelections { get; set; }
        /// 
        /// Gets or sets a value indicating whether to remember subtitle selections on played content.
        /// 
        /// 
        /// Required.
        /// 
        public bool RememberSubtitleSelections { get; set; }
        /// 
        /// Gets or sets a value indicating whether to enable auto-play for the next episode.
        /// 
        /// 
        /// Required.
        /// 
        public bool EnableNextEpisodeAutoPlay { get; set; }
        /// 
        /// Gets or sets a value indicating whether the user should auto-login.
        /// 
        /// 
        /// Required.
        /// 
        public bool EnableAutoLogin { get; set; }
        /// 
        /// Gets or sets a value indicating whether the user can change their preferences.
        /// 
        /// 
        /// Required.
        /// 
        public bool EnableUserPreferenceAccess { get; set; }
        /// 
        /// Gets or sets the maximum parental age rating.
        /// 
        public int? MaxParentalAgeRating { get; set; }
        /// 
        /// Gets or sets the remote client bitrate limit.
        /// 
        public int? RemoteClientBitrateLimit { get; set; }
        /// 
        /// Gets or sets the internal id.
        /// This is a temporary stopgap for until the library db is migrated.
        /// This corresponds to the value of the index of this user in the library db.
        /// 
        public long InternalId { get; set; }
        /// 
        /// Gets or sets the user's profile image. Can be null.
        /// 
        // [ForeignKey("UserId")]
        public virtual ImageInfo ProfileImage { get; set; }
        /// 
        /// Gets or sets the user's display preferences.
        /// 
        /// 
        /// Required.
        /// 
        [Required]
        public virtual DisplayPreferences DisplayPreferences { get; set; }
        /// 
        /// Gets or sets the level of sync play permissions this user has.
        /// 
        public SyncPlayAccess SyncPlayAccess { get; set; }
        /// 
        /// Gets or sets the row version.
        /// 
        /// 
        /// Required, Concurrency Token.
        /// 
        [ConcurrencyCheck]
        public uint RowVersion { get; set; }
        /// 
        /// Gets or sets the list of access schedules this user has.
        /// 
        public virtual ICollection AccessSchedules { get; protected set; }
        /// 
        /// Gets or sets the list of item display preferences.
        /// 
        public virtual ICollection ItemDisplayPreferences { get; protected set; }
        /*
        /// 
        /// Gets or sets the list of groups this user is a member of.
        /// 
        [ForeignKey("Group_Groups_Guid")]
        public virtual ICollection Groups { get; protected set; }
        */
        /// 
        /// Gets or sets the list of permissions this user has.
        /// 
        [ForeignKey("Permission_Permissions_Guid")]
        public virtual ICollection Permissions { get; protected set; }
        /*
        /// 
        /// Gets or sets the list of provider mappings this user has.
        /// 
        [ForeignKey("ProviderMapping_ProviderMappings_Id")]
        public virtual ICollection ProviderMappings { get; protected set; }
        */
        /// 
        /// Gets or sets the list of preferences this user has.
        /// 
        [ForeignKey("Preference_Preferences_Guid")]
        public virtual ICollection Preferences { get; protected set; }
        /// 
        public void OnSavingChanges()
        {
            RowVersion++;
        }
        /// 
        /// Checks whether the user has the specified permission.
        /// 
        /// The permission kind.
        /// True if the user has the specified permission.
        public bool HasPermission(PermissionKind kind)
        {
            return Permissions.First(p => p.Kind == kind).Value;
        }
        /// 
        /// Sets the given permission kind to the provided value.
        /// 
        /// The permission kind.
        /// The value to set.
        public void SetPermission(PermissionKind kind, bool value)
        {
            Permissions.First(p => p.Kind == kind).Value = value;
        }
        /// 
        /// Gets the user's preferences for the given preference kind.
        /// 
        /// The preference kind.
        /// A string array containing the user's preferences.
        public string[] GetPreference(PreferenceKind preference)
        {
            var val = Preferences.First(p => p.Kind == preference).Value;
            return Equals(val, string.Empty) ? Array.Empty() : val.Split(Delimiter);
        }
        /// 
        /// Sets the specified preference to the given value.
        /// 
        /// The preference kind.
        /// The values.
        public void SetPreference(PreferenceKind preference, string[] values)
        {
            Preferences.First(p => p.Kind == preference).Value
                = string.Join(Delimiter.ToString(CultureInfo.InvariantCulture), values);
        }
        /// 
        /// Checks whether this user is currently allowed to use the server.
        /// 
        /// True if the current time is within an access schedule, or there are no access schedules.
        public bool IsParentalScheduleAllowed()
        {
            return AccessSchedules.Count == 0
                   || AccessSchedules.Any(i => IsParentalScheduleAllowed(i, DateTime.UtcNow));
        }
        /// 
        /// Checks whether the provided folder is in this user's grouped folders.
        /// 
        /// The Guid of the folder.
        /// True if the folder is in the user's grouped folders.
        public bool IsFolderGrouped(Guid id)
        {
            return GetPreference(PreferenceKind.GroupedFolders).Any(i => new Guid(i) == id);
        }
        private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date)
        {
            var localTime = date.ToLocalTime();
            var hour = localTime.TimeOfDay.TotalHours;
            return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek)
                   && hour >= schedule.StartHour
                   && hour <= schedule.EndHour;
        }
        // TODO: make these user configurable?
        private void AddDefaultPermissions()
        {
            Permissions.Add(new Permission(PermissionKind.IsAdministrator, false));
            Permissions.Add(new Permission(PermissionKind.IsDisabled, false));
            Permissions.Add(new Permission(PermissionKind.IsHidden, true));
            Permissions.Add(new Permission(PermissionKind.EnableAllChannels, true));
            Permissions.Add(new Permission(PermissionKind.EnableAllDevices, true));
            Permissions.Add(new Permission(PermissionKind.EnableAllFolders, true));
            Permissions.Add(new Permission(PermissionKind.EnableContentDeletion, false));
            Permissions.Add(new Permission(PermissionKind.EnableContentDownloading, true));
            Permissions.Add(new Permission(PermissionKind.EnableMediaConversion, true));
            Permissions.Add(new Permission(PermissionKind.EnableMediaPlayback, true));
            Permissions.Add(new Permission(PermissionKind.EnablePlaybackRemuxing, true));
            Permissions.Add(new Permission(PermissionKind.EnablePublicSharing, true));
            Permissions.Add(new Permission(PermissionKind.EnableRemoteAccess, true));
            Permissions.Add(new Permission(PermissionKind.EnableSyncTranscoding, true));
            Permissions.Add(new Permission(PermissionKind.EnableAudioPlaybackTranscoding, true));
            Permissions.Add(new Permission(PermissionKind.EnableLiveTvAccess, true));
            Permissions.Add(new Permission(PermissionKind.EnableLiveTvManagement, true));
            Permissions.Add(new Permission(PermissionKind.EnableSharedDeviceControl, true));
            Permissions.Add(new Permission(PermissionKind.EnableVideoPlaybackTranscoding, true));
            Permissions.Add(new Permission(PermissionKind.ForceRemoteSourceTranscoding, false));
            Permissions.Add(new Permission(PermissionKind.EnableRemoteControlOfOtherUsers, false));
        }
        private void AddDefaultPreferences()
        {
            foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast())
            {
                Preferences.Add(new Preference(val, string.Empty));
            }
        }
    }
}