using InABox.API;
using InABox.Clients;
using InABox.Core;
using InABox.Database;

namespace InABox.Rpc
{
    
    public class RpcValidateHandler : RpcCommandHandler<IRpcServer, RpcValidateCommand, RpcValidateParameters, RpcValidateResult> 
    {
        protected override RpcValidateResult Execute(IRpcSession session, RpcValidateParameters? parameters, Logger logger)
        {
            if (parameters == null)
                return new RpcValidateResult() { Status = ValidationStatus.INVALID };
            
            session.Platform = parameters.Platform;
            session.Version = parameters.Version;
            
            User? user = null;
            bool reLogin = false;

            if (parameters.SessionID != Guid.Empty)
            {
                user = CredentialsCache.Validate(parameters.SessionID);
                if (user != null)
                {
                    logger.Send(LogType.Information, "", $"{parameters.SessionID} re-logged in!");
                    CredentialsCache.RefreshSessionExpiry(parameters.SessionID);
                    reLogin = true;
                }
                else
                {
                    logger.Send(LogType.Information, "", $"{parameters.SessionID} re-logging failed!");
                }
            }

            if (user is null)
            {
                if (parameters.UsePIN)
                {
                    logger.Send(LogType.Information, "", $"Login request for PIN {parameters.PIN}");
                    user = CredentialsCache.ValidateUser(parameters.PIN);
                }
                else
                {
                    var userID = parameters.UserID;
                    var password = parameters.Password;
                    user = CredentialsCache.ValidateUser(userID, password);
                    if (user?.ID != CoreUtils.FullGuid)
                    {
                        logger.Send(LogType.Information, userID, $"Login request for {userID}");
                    }
                }
            }
            
            if (user == null)
            {
                logger.Send(LogType.Information, "", $"Login failed!");
                return new RpcValidateResult() { Status = ValidationStatus.INVALID };
            }

            Guid? sessionID = null;
            var result = new RpcValidateResult()
            {
                Status = ValidationStatus.VALID,
                UserGuid = user.ID,
                UserID = user.UserID,
                SecurityID = user.SecurityGroup.ID,
                PasswordExpiration = UserStore.PasswordExpirationTime > TimeSpan.Zero ? user.PasswordExpiration : DateTime.MinValue
            };

            if (user.ID != CoreUtils.FullGuid)
            {
                if (UserStore.PasswordExpirationTime != TimeSpan.Zero
                    && user.PasswordExpiration > DateTime.MinValue
                    && user.PasswordExpiration < DateTime.Now
                    && !parameters.UsePIN)
                {
                    logger.Send(LogType.Information, user.UserID, $"Password for ({user.UserID}) has expired!");
                    result.Status = ValidationStatus.PASSWORD_EXPIRED;
                }
                else if (reLogin)
                {
                    logger.Send(LogType.Information, user.UserID, $"Login ({user.UserID}) success!");
                }
                else if (user.Use2FA)
                {
                    logger.Send(LogType.Information, user.UserID, $"Login ({user.UserID}) requires 2FA. Sending code...");
                    sessionID = CredentialsCache.SendCode(user.ID, out var recipient)
                        ?? throw new Exception("Code failed to send!");

                    result.Status = ValidationStatus.REQUIRE_2FA;
                    result.Recipient2FA = recipient;
                }
            }
            result.SessionID = sessionID ??
                (reLogin
                ? parameters.SessionID
                : user.ID == CoreUtils.FullGuid
                    ? CredentialsCache.NewSession(user, true, DateTime.MaxValue)
                    : CredentialsCache.NewSession(user, true));

            // Store the CredentialsCache ID against the ServerSession
            session.ID = result.SessionID;
            session.UserID = user?.UserID ?? "";
            
            logger.Send(LogType.Information, user?.UserID ?? "", $"Validation Status is {result.Status}");
            
            return result;
        }

        public RpcValidateHandler(IRpcServer sender) : base(sender)
        {
        }
    }
}