#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Persistence;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
{
    /// 
    /// Class SQLiteUserRepository
    /// 
    public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
    {
        private readonly JsonSerializerOptions _jsonOptions;
        public SqliteUserRepository(
            ILogger logger,
            IServerApplicationPaths appPaths)
            : base(logger)
        {
            _jsonOptions = JsonDefaults.GetOptions();
            DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
        }
        /// 
        /// Gets the name of the repository
        /// 
        /// The name.
        public string Name => "SQLite";
        /// 
        /// Opens the connection to the database.
        /// 
        public void Initialize()
        {
            using (var connection = GetConnection())
            {
                var localUsersTableExists = TableExists(connection, "LocalUsersv2");
                connection.RunQueries(new[] {
                    "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)",
                    "drop index if exists idx_users"
                });
                if (!localUsersTableExists && TableExists(connection, "Users"))
                {
                    TryMigrateToLocalUsersTable(connection);
                }
                RemoveEmptyPasswordHashes(connection);
            }
        }
        private void TryMigrateToLocalUsersTable(ManagedConnection connection)
        {
            try
            {
                connection.RunQueries(new[]
                {
                    "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users"
                });
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Error migrating users database");
            }
        }
        private void RemoveEmptyPasswordHashes(ManagedConnection connection)
        {
            foreach (var user in RetrieveAllUsers(connection))
            {
                // If the user password is the sha1 hash of the empty string, remove it
                if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
                    && !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
                {
                    continue;
                }
                user.Password = null;
                var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
                connection.RunInTransaction(db =>
                {
                    using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
                    {
                        statement.TryBind("@InternalId", user.InternalId);
                        statement.TryBind("@data", serialized);
                        statement.MoveNext();
                    }
                }, TransactionMode);
            }
        }
        /// 
        /// Save a user in the repo
        /// 
        public void CreateUser(User user)
        {
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }
            var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
            using (var connection = GetConnection())
            {
                connection.RunInTransaction(db =>
                {
                    using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
                    {
                        statement.TryBind("@guid", user.Id.ToByteArray());
                        statement.TryBind("@data", serialized);
                        statement.MoveNext();
                    }
                    var createdUser = GetUser(user.Id, connection);
                    if (createdUser == null)
                    {
                        throw new ApplicationException("created user should never be null");
                    }
                    user.InternalId = createdUser.InternalId;
                }, TransactionMode);
            }
        }
        public void UpdateUser(User user)
        {
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }
            var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
            using (var connection = GetConnection())
            {
                connection.RunInTransaction(db =>
                {
                    using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
                    {
                        statement.TryBind("@InternalId", user.InternalId);
                        statement.TryBind("@data", serialized);
                        statement.MoveNext();
                    }
                }, TransactionMode);
            }
        }
        private User GetUser(Guid guid, ManagedConnection connection)
        {
            using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid"))
            {
                statement.TryBind("@guid", guid);
                foreach (var row in statement.ExecuteQuery())
                {
                    return GetUser(row);
                }
            }
            return null;
        }
        private User GetUser(IReadOnlyList row)
        {
            var id = row[0].ToInt64();
            var guid = row[1].ReadGuidFromBlob();
            var user = JsonSerializer.Deserialize(row[2].ToBlob(), _jsonOptions);
            user.InternalId = id;
            user.Id = guid;
            return user;
        }
        /// 
        /// Retrieve all users from the database
        /// 
        /// IEnumerable{User}.
        public List RetrieveAllUsers()
        {
            using (var connection = GetConnection(true))
            {
                return new List(RetrieveAllUsers(connection));
            }
        }
        /// 
        /// Retrieve all users from the database
        /// 
        /// IEnumerable{User}.
        private IEnumerable RetrieveAllUsers(ManagedConnection connection)
        {
            foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
            {
                yield return GetUser(row);
            }
        }
        /// 
        /// Deletes the user.
        /// 
        /// The user.
        /// Task.
        /// user
        public void DeleteUser(User user)
        {
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }
            using (var connection = GetConnection())
            {
                connection.RunInTransaction(db =>
                {
                    using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id"))
                    {
                        statement.TryBind("@id", user.InternalId);
                        statement.MoveNext();
                    }
                }, TransactionMode);
            }
        }
    }
}