using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.ObjectModel;
using LeanCloud.Storage;
using LeanCloud.Realtime.Internal.Protocol;
using LeanCloud.Realtime.Internal.Controller;

namespace LeanCloud.Realtime {
    /// <summary>
    /// LCIMClient is a local representation of realtime client in LeanCloud.
    /// </summary>
    public class LCIMClient {
        /// <summary>
        /// Conversation cache
        /// </summary>
        internal Dictionary<string, LCIMConversation> ConversationDict;

        /// <summary>
        /// Client Id
        /// </summary>
        public string Id {
            get; private set;
        }

        /// <summary>
        /// Client tag
        /// </summary>
        public string Tag {
            get; private set;
        }

        /// <summary>
        /// Device Id
        /// </summary>
        public string DeviceId {
            get; private set;
        }

        internal string SessionToken {
            get; private set;
        }

        #region 连接状态事件

        /// <summary>
        /// Occurs when the connection is lost.
        /// </summary>
        public Action OnPaused {
            get; set;
        }

        /// <summary>
        /// Occurs when the connection is recovered. 
        /// </summary>
        public Action OnResume {
            get; set;
        }

        /// <summary>
        /// Occurs when the connection is closed and there will be no auto reconnection.
        /// Possible causes include there is a single device login conflict or the client has been kicked off by the server.
        /// </summary>
        public Action<int, string> OnClose {
            get; set;
        }

        #endregion

        #region 对话事件

        /// <summary>
        /// Occurs when the current user is added into the blacklist of a conversation.
        /// </summary>
        public Action<LCIMConversation, string> OnBlocked {
            get; set;
        }

        /// <summary>
        /// Occurs when the current user is removed from the blacklist of a conversation.
        /// </summary>
        public Action<LCIMConversation, string> OnUnblocked {
            get; set;
        }

        /// <summary>
        /// Occurs when the current user is muted in a conversation.
        /// </summary>
        public Action<LCIMConversation, string> OnMuted;

        /// <summary>
        /// Occurs when the current user is unmuted in a conversation. 
        /// </summary>
        public Action<LCIMConversation, string> OnUnmuted;

        /// <summary>
        /// Occurs when the properties of a conversation are updated.
        /// </summary>
        public Action<LCIMConversation, ReadOnlyDictionary<string, object>, string> OnConversationInfoUpdated;

        /// <summary>
        /// Occurs when the current user is invited to a conversation.
        /// </summary>
        public Action<LCIMConversation, string> OnInvited {
            get; set;
        }

        /// <summary>
        /// Occurs when the current user is kicked from a conversation.
        /// </summary>
        public Action<LCIMConversation, string> OnKicked {
            get; set;
        }

        /// <summary>
        /// Occurs when a user joined a conversation.
        /// </summary>
        public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersJoined {
            get; set;
        }

        /// <summary>
        /// Occurs when a user left a conversation. 
        /// </summary>
        public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersLeft {
            get; set;
        }

        /// <summary>
        /// Occurs when a user is added to the blacklist of a conversation.
        /// </summary>
        public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersBlocked {
            get; set;
        }

        /// <summary>
        /// Occurs when a user is removed from the blacklist of a conversation. 
        /// </summary>
        public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersUnblocked {
            get; set;
        }

        /// <summary>
        /// Occurs when a user is muted in a conversation. 
        /// </summary>
        public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersMuted {
            get; set;
        }

        /// <summary>
        /// Occurs when a user is unmuted in a conversation.
        /// </summary>
        public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersUnmuted {
            get; set;
        }

        /// <summary>
        /// Occurs when the properties of someone are updated.
        /// </summary>
        public Action<LCIMConversation, string, string, string> OnMemberInfoUpdated;

        #endregion

        #region 消息事件

        /// <summary>
        /// Occurs when a new message is delivered to a conversation the current user is already in.
        /// </summary>
        public Action<LCIMConversation, LCIMMessage> OnMessage {
            get; set;
        }

        /// <summary>
        /// Occurs when a message is recalled.
        /// </summary>
        public Action<LCIMConversation, LCIMRecalledMessage> OnMessageRecalled {
            get; set;
        }

        /// <summary>
        /// Occurs when a message is updated.
        /// </summary>
        public Action<LCIMConversation, LCIMMessage> OnMessageUpdated {
            get; set;
        }

        /// <summary>
        /// Occurs when a message is delivered.
        /// </summary>
        public Action<LCIMConversation, string> OnMessageDelivered {
            get; set;
        }

        /// <summary>
        /// Occurs when a message is read.
        /// </summary>
        public Action<LCIMConversation, string> OnMessageRead {
            get; set;
        }

        /// <summary>
        /// Occurs when the number of unreadMessagesCount is updatded.
        /// </summary>
        public Action<ReadOnlyCollection<LCIMConversation>> OnUnreadMessagesCountUpdated {
            get; set;
        }

        /// <summary>
        /// Occurs when the last delivered message is updated.
        /// </summary>
        public Action<LCIMConversation> OnLastDeliveredAtUpdated {
            get; set;
        }

        /// <summary>
        /// Occurs when the last read message is updated.
        /// </summary>
        public Action<LCIMConversation> OnLastReadAtUpdated {
            get; set;
        }

        #endregion

        internal ILCIMSignatureFactory SignatureFactory {
            get; private set;
        }

        internal LCIMSessionController SessionController {
            get; private set;
        }

        internal LCIMMessageController MessageController {
            get; private set;
        }

        internal LCIMConversationController ConversationController {
            get; private set;
        }

        #region 接口

        /// <summary>
        /// Constructs a LCIMClient with client id.
        /// </summary>
        /// <param name="clientId"></param>
        /// <param name="tag"></param>
        /// <param name="deviceId"></param>
        /// <param name="signatureFactory"></param>
        public LCIMClient(string clientId,
            string tag = null,
            string deviceId = null,
            ILCIMSignatureFactory signatureFactory = null) {
            if (string.IsNullOrEmpty(clientId)) {
                throw new ArgumentNullException(nameof(clientId));
            }
            SetUpClient(clientId, tag, deviceId, signatureFactory);
        }

        /// <summary>
        /// Constructs a LCIMClient with a LCUser.
        /// </summary>
        /// <param name="user"></param>
        /// <param name="tag"></param>
        /// <param name="deviceId"></param>
        /// <param name="signatureFactory"></param>
        public LCIMClient(LCUser user,
            string tag = null,
            string deviceId = null,
            ILCIMSignatureFactory signatureFactory = null) {
            if (user == null) {
                throw new ArgumentNullException(nameof(user));
            }
            if (string.IsNullOrEmpty(user.ObjectId) ||
                string.IsNullOrEmpty(user.SessionToken)) {
                throw new ArgumentException("User must be authenticacted.");
            }
            SetUpClient(user.ObjectId, tag, deviceId, signatureFactory);
            SessionToken = user.SessionToken;
        }

        private void SetUpClient(string clientId,
            string tag,
            string deviceId,
            ILCIMSignatureFactory signatureFactory) {
            Id = clientId;
            Tag = tag;
            DeviceId = deviceId;
            SignatureFactory = signatureFactory;

            ConversationDict = new Dictionary<string, LCIMConversation>();

            // 模块
            SessionController = new LCIMSessionController(this);
            ConversationController = new LCIMConversationController(this);
            MessageController = new LCIMMessageController(this);
        }

        /// <summary>
        /// Signing in
        /// </summary>
        /// <param name="force">If this is ture (default value), and single device sign-on is enabled, users already logged in on another device with the same tag will be logged out.</param>
        /// <returns></returns>
        public async Task Open(bool force = true) {
            try {
                // 打开 Session
                await SessionController.Open(force);
            } catch (Exception e) {
                LCLogger.Error(e);
                // 如果 session 阶段异常，则关闭连接
                throw e;
            }
        }

        /// <summary>
        /// Closes the session
        /// </summary>
        /// <returns></returns>
        public async Task Close() {
            // 关闭 session
            await SessionController.Close();
        }

        /// <summary>
        /// Creates a conversation
        /// </summary>
        /// <param name="members">The list of clientIds of participants in this conversation (except the creator)</param>
        /// <param name="name">The name of this conversation</param>
        /// <param name="unique">Whether this conversation is unique;
        /// if it is true and an existing conversation contains the same composition of members,
        /// the existing conversation will be reused, otherwise a new conversation will be created.</param>
        /// <param name="properties">Custom attributes of this conversation</param>
        /// <returns></returns>
        public async Task<LCIMConversation> CreateConversation(
            IEnumerable<string> members,
            string name = null,
            bool unique = true,
            Dictionary<string, object> properties = null) {
            return await ConversationController.CreateConv(members: members,
                name: name,
                unique: unique,
                properties: properties);
        }

        /// <summary>
        /// Creates a chatroom
        /// </summary>
        /// <param name="name">The name of this chatroom</param>
        /// <param name="properties">Custom attributes of this chatroom</param>
        /// <returns></returns>
        public async Task<LCIMChatRoom> CreateChatRoom(
            string name,
            Dictionary<string, object> properties = null) {
            LCIMChatRoom chatRoom = await ConversationController.CreateConv(name: name,
                transient: true,
                properties: properties) as LCIMChatRoom;
            return chatRoom;
        }

        /// <summary>
        /// Creates a temporary conversation
        /// </summary>
        /// <param name="members">The list of clientIds of participants in this temporary conversation (except the creator)</param>
        /// <param name="ttl">TTL of this temporary conversation</param>
        /// <param name="properties">Custom attributes of this temporary conversation</param>
        /// <returns></returns>
        public async Task<LCIMTemporaryConversation> CreateTemporaryConversation(
            IEnumerable<string> members,
            int ttl = 86400,
            Dictionary<string, object> properties = null) {
            LCIMTemporaryConversation tempConversation = await ConversationController.CreateConv(members: members,
                temporary: true,
                temporaryTtl: ttl,
                properties: properties) as LCIMTemporaryConversation;
            return tempConversation;
        }

        /// <summary>
        /// Queries a conversation based on its id.
        /// </summary>
        /// <param name="id">objectId</param>
        /// <returns></returns>
        public async Task<LCIMConversation> GetConversation(string id) {
            if (string.IsNullOrEmpty(id)) {
                throw new ArgumentNullException(nameof(id));
            }
            if (LCIMConversation.IsTemporayConversation(id)) {
                List<LCIMTemporaryConversation> temporaryConversationList = await ConversationController.GetTemporaryConversations(new string[] { id });
                if (temporaryConversationList == null || temporaryConversationList.Count < 1) {
                    return null;
                }
                return temporaryConversationList[0];
            }
            LCIMConversationQuery query = GetQuery()
                .WhereEqualTo("objectId", id)
                .Limit(1);
            ReadOnlyCollection<LCIMConversation> results = await ConversationController.Find(query);
            if (results == null || results.Count < 1) {
                return null;
            }
            return results[0];
        }

        /// <summary>
        /// Queries conversations based on their ids.
        /// </summary>
        /// <param name="ids">objectId list</param>
        /// <returns></returns>
        public async Task<ReadOnlyCollection<LCIMConversation>> GetConversationList(IEnumerable<string> ids) {
            if (ids == null || ids.Count() == 0) {
                throw new ArgumentNullException(nameof(ids));
            }
            // 区分临时对话
            IEnumerable<string> tempConvIds = ids.Where(item => {
                return LCIMConversation.IsTemporayConversation(item);
            });
            IEnumerable<string> convIds = ids.Where(item => {
                return !tempConvIds.Contains(item);
            });
            List<LCIMConversation> conversationList = new List<LCIMConversation>();
            if (tempConvIds.Count() > 0) {
                List<LCIMTemporaryConversation> temporaryConversations = await ConversationController.GetTemporaryConversations(tempConvIds);
                conversationList.AddRange(temporaryConversations);
            }
            if (convIds.Count() > 0) {
                LCIMConversationQuery query = GetQuery()
                    .WhereContainedIn("objectId", convIds)
                    .Limit(convIds.Count());
                ReadOnlyCollection<LCIMConversation> conversations = await ConversationController.Find(query);
                conversationList.AddRange(conversations);
            }
            return conversationList.AsReadOnly();
        }

        /// <summary>
        /// Constructs a conversation query.
        /// </summary>
        /// <returns></returns>
        public LCIMConversationQuery GetQuery() {
            return new LCIMConversationQuery(this);
        }

        #endregion

        internal void HandleNotification(GenericCommand notification) {
            switch (notification.Cmd) {
                case CommandType.Session:
                    SessionController.HandleNotification(notification);
                    break;
                case CommandType.Conv:
                case CommandType.Unread:
                    ConversationController.HandleNotification(notification);
                    break;
                case CommandType.Direct:
                case CommandType.Patch:
                case CommandType.Rcp:
                    MessageController.HandleNotification(notification);
                    break;
                default:
                    break;
            }
        }

        internal void HandleDisconnected() {
            OnPaused?.Invoke();
        }

        internal async void HandleReconnected() {
            try {
                // 打开 Session
                await SessionController.Reopen();
                // 回调用户
                OnResume?.Invoke();
            } catch (LCException e) {
                LCLogger.Error(e);
                // 重连成功，但 session/open 失败
                OnClose?.Invoke(e.Code, e.Message);
            }
        }
        
        internal async Task<LCIMConversation> GetOrQueryConversation(string convId) {
            if (ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) {
                return conversation;
            }
            conversation = await GetConversation(convId);
            return conversation;
        }
    }
}
