using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LeanCloud.Realtime.Internal.Protocol;

namespace LeanCloud.Realtime.Internal.Controller {
    internal class LCIMSessionController : LCIMController {
        private string token;
        private DateTimeOffset expiredAt;

        internal LCIMSessionController(LCIMClient client)
            : base(client) {

        }

        #region 内部接口

        /// <summary>
        /// 打开会话
        /// </summary>
        /// <returns></returns>
        internal async Task Open(bool force) {
            SessionCommand session = await NewSessionCommand();
            session.R = !force;
            session.ConfigBitmap = 0x2B;
            GenericCommand request = NewCommand(CommandType.Session, OpType.Open);
            request.SessionMessage = session;
            GenericCommand response = await Client.Connection.SendRequest(request);
            UpdateSession(response.SessionMessage);
        }

        /// <summary>
        /// 重新打开会话，重连时调用
        /// </summary>
        /// <returns></returns>
        internal async Task Reopen() {
            SessionCommand session = await NewSessionCommand();
            session.R = true;
            GenericCommand request = NewCommand(CommandType.Session, OpType.Open);
            request.SessionMessage = session;
            GenericCommand response = await Client.Connection.SendRequest(request);
            if (response.Op == OpType.Opened) {
                UpdateSession(response.SessionMessage);
            } else if (response.Op == OpType.Closed) {
                await OnClosed(response.SessionMessage);
            }
        }

        /// <summary>
        /// 关闭会话
        /// </summary>
        /// <returns></returns>
        internal async Task Close() {
            GenericCommand request = NewCommand(CommandType.Session, OpType.Close);
            await Client.Connection.SendRequest(request);
        }

        /// <summary>
        /// 获取可用 token
        /// </summary>
        /// <returns></returns>
        internal async Task<string> GetToken() {
            if (IsExpired) {
                await Refresh();
            }
            return token;
        }

        #endregion

        private async Task Refresh() {
            SessionCommand session = await NewSessionCommand();
            GenericCommand request = NewCommand(CommandType.Session, OpType.Refresh);
            request.SessionMessage = session;
            GenericCommand response = await Client.Connection.SendRequest(request);
            UpdateSession(response.SessionMessage);
        }

        private async Task<SessionCommand> NewSessionCommand() {
            SessionCommand session = new SessionCommand();
            if (Client.Tag != null) {
                session.Tag = Client.Tag;
            }
            if (Client.DeviceId != null) {
                session.DeviceId = Client.DeviceId;
            }
            LCIMSignature signature = null;
            if (Client.SignatureFactory != null) {
                signature = await Client.SignatureFactory.CreateConnectSignature(Client.Id);
            }
            if (signature == null && !string.IsNullOrEmpty(Client.SessionToken)) {
                Dictionary<string, object> ret = await LCApplication.HttpClient.Post<Dictionary<string, object>>("rtm/sign", data: new Dictionary<string, object> {
                    { "session_token", Client.SessionToken }
                });
                signature = new LCIMSignature {
                    Signature = ret["signature"] as string,
                    Timestamp = (long)ret["timestamp"],
                    Nonce = ret["nonce"] as string
                };
            }
            if (signature != null) {
                session.S = signature.Signature;
                session.T = signature.Timestamp;
                session.N = signature.Nonce;
            }
            return session;
        }

        private void UpdateSession(SessionCommand session) {
            token = session.St;
            int ttl = session.StTtl;
            expiredAt = DateTimeOffset.Now + TimeSpan.FromSeconds(ttl);
        }

        private bool IsExpired {
            get {
                return DateTimeOffset.Now > expiredAt;
            }
        }

        #region 消息处理

        internal override async Task OnNotification(GenericCommand notification) {
            switch (notification.Op) {
                case OpType.Closed:
                    await OnClosed(notification.SessionMessage);
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// 被关闭
        /// </summary>
        /// <param name="session"></param>
        /// <returns></returns>
        private async Task OnClosed(SessionCommand session) {
            int code = session.Code;
            string reason = session.Reason;
            string detail = session.Detail;
            await Connection.Close();
            Client.OnClose?.Invoke(code, reason);
        }

        #endregion
    }
}
