using System;
using Kreta.Framework.Caching;
using Kreta.Framework.Security;

namespace Kreta.Framework.Session
{
    public class SessionManager : IDisposable
    {
        private readonly LoginInfoCache loginInfoCache;

        private readonly string serverName;

        public event SessionCreatedEventHandler SessionCreated;

        public event SessionDeletedEventHandler SessionDeleted;

        public bool IsSessionAlive(string sessionid)
        {
            return loginInfoCache.GetLoginInfo(sessionid) != null;
        }

        public SessionManager(Configuration configuration, Caching.CacheManager cacheManager)
        {
            if (configuration == null)
            {
                throw new ArgumentNullException(nameof(configuration));
            }

            if (configuration.SessionTimeout > int.MaxValue / (1000 * 60) || configuration.SessionTimeout < 0)
            {
                throw new InvalidConfigurationException("SessionTimeout is out of range.");
            }

            serverName = configuration.ServerName;
            loginInfoCache = cacheManager.AquireCache<LoginInfoCache>();
            loginInfoCache.SessionTimeout = configuration.SessionTimeout;
        }

        public virtual UserContext ActivateSystemSession()
        {
            var session = new SystemSession();
            session.Activate();
            return session;
        }

        public virtual void DeactivateSystemSession()
        {
            UserContext session = UserContext.Instance;
            if (session != null)
            {
                session.DeActivate();
            }
        }

        public virtual UserContext ActivateServiceSystemSession(string connectionString)
        {
            var session = new ServiceSystemSession(connectionString);
            session.Activate();
            return session;
        }

        public virtual void DeactivateServiceSystemSession()
        {
            UserContext session = UserContext.Instance;
            if (session != null)
            {
                session.DeActivate();
            }
        }

        public virtual UserContext ActivateOrganizationSystemSession(string organizationIdentifier)
        {
            var session = new OrganizationSystemSession(organizationIdentifier);
            session.Activate();

            return session;
        }

        public virtual void DeactivateOrganizationSystemSession()
        {
            UserContext session = UserContext.Instance;
            if (session != null)
            {
                session.DeActivate();
            }
        }

        public virtual UserContext ActivateSession(string sessionId)
        {
            LoginInfo loginInfo = loginInfoCache.GetLoginInfo(sessionId);
            if (loginInfo == null)
            {
                throw new InvalidSessionException(sessionId, SessionReason.NOT_EXISTS);
            }

            var session = new UserContext(loginInfo);
            session.Activate();

            return session;
        }

        public void DeActivateSession(string sessionId)
        {
            LoginInfo loginInfo = loginInfoCache.GetLoginInfo(sessionId);
            if (loginInfo == null)
            {
                throw new InvalidSessionException(sessionId, SessionReason.NOT_EXISTS);
            }

            var session = UserContext.Instance;
            if (session != null)
            {
                session.DeActivate();
            }
        }

        public void CreateSession(LoginInfo loginInfo)
        {
            if (loginInfoCache.IsExistsLoginInfo(loginInfo.SessionID))
            {
                throw new InvalidSessionException(loginInfo.SessionID, SessionReason.EXISTS);
            }

            loginInfoCache.PutLoginInfo(loginInfo);

            DoSessionCreated(loginInfo, serverName);
        }

        protected virtual void DoSessionCreated(LoginInfo loginInfo, string serverName)
        {
            SessionCreated?.Invoke(this, new SessionEventArgs(loginInfo, serverName));
        }

        public void PingSession(string sessionId)
        {
            loginInfoCache.Ping(sessionId);
        }

        public void DeleteSession(string sessionId)
        {
            LoginInfo loginInfo = loginInfoCache.GetLoginInfo(sessionId);
            if (loginInfo == null)
            {
                return;
            }

            loginInfoCache.RemoveLoginInfo(sessionId);

            DeleteSession(loginInfo);
        }

        public void DeleteSession(LoginInfo loginInfo)
        {
            DoSessionDeleted(loginInfo, serverName);
        }

        protected virtual void DoSessionDeleted(LoginInfo loginInfo, string serverName)
        {
            SessionDeleted?.Invoke(this, new SessionEventArgs(loginInfo, serverName));
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }
    }
}