using Ease.NetCore.DataAccess;
using Ease.NetCore.DataAccess.SQL;
using Ease.NetCore.Utility;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Options;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems
{
    public class UserService(IOptions<AppSettings> settings, IOptions<MenuSettings> menuSettings, IRefreshTokenService refreshTokenService) : IUserService
    {
        private readonly AppSettings _settings = settings?.Value;
        private readonly MenuSettings _menuSettings = menuSettings?.Value;
        private readonly IRefreshTokenService _refreshTokenService = refreshTokenService;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="ipAddress"></param>
        /// <param name="checkPwd"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task<User> LoginAsync(LoginRequest request, string ipAddress, bool checkPwd)
        {
            User user = new() { LoginId = request.LoginId, LoginStatus = EnumLoginStatus.Unsuccessful };
            //try
            //{
            //    string password = EncryptPassword(password: request.Password);

            //    using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
            //    try
            //    {
            //        #region Attendance Login and Valid Ip Address

            //        if (request.AttendanceLogin && !request.LoginId.ToLower().Equals(User.SuperUser_LoginId))
            //        {
            //            string errMsg = string.Empty;
            //            SqlParameter[] p =
            //            [
            //                SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: request.IpAddress, size: 20),
            //                SqlHelperExtension.CreateInParam(pName: "@MacAddress", pType: SqlDbType.VarChar, pValue: request.MacAddress, size: 30),
            //                SqlHelperExtension.CreateInParam(pName: "@HostName", pType: SqlDbType.VarChar, pValue: request.HostName, size: 100),
            //                SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: request.LoginId, size: 50),
            //                SqlHelperExtension.CreateOutParam(pName: "@ErrMsg", pType: SqlDbType.VarChar, pValue: errMsg, size: 300)
            //            ];
            //            _ = tc.ExecuteNonQuerySp(spName: "dbo.IsValidIpOrMacAddress", parameterValues: p);

            //            errMsg = (p[4] == null || p[4].Value == null || p[4].Value == DBNull.Value) ? string.Empty : Convert.ToString(p[4].Value);
            //            if (!string.IsNullOrEmpty(errMsg))
            //            {
            //                tc.End();
            //                throw new InvalidOperationException(errMsg);
            //            }
            //        }

            //        #endregion

            //        bool batchEnabled = false;
            //        DateTime sysDate = DateTime.Today.Date;
            //        string appVer = string.Empty, alParams = string.Empty;
            //        int maxTryCount = 5, lockTime = 1, marMonths = 24, idleTime = 0, timeoutTime = 0, pingTime = 0, bmProcessId = 0, prProcessId = 0;

            //        #region Read Params from ThisSystem

            //        using (IDataReader dr = tc.ExecuteReader("SELECT MaxTryCount, LockTime, MarMonths, CAST(GETDATE() as date), AppVersion, AutoLogoutParams, BatchEnabled, BmProcessId, PrProcessId FROM ThisSystem"))
            //        {
            //            if (dr.Read())
            //            {
            //                maxTryCount = dr.GetInt16(0);
            //                lockTime = dr.GetInt16(1);
            //                marMonths = dr.GetInt16(2);
            //                sysDate = dr.GetDateTime(3);
            //                appVer = dr.GetString(4);
            //                alParams = dr.IsDBNull(5) ? string.Empty : dr.GetString(5);
            //                batchEnabled = !dr.IsDBNull(6) && dr.GetInt16(6) != 0;
            //                bmProcessId = dr.IsDBNull(7) ? 0 : dr.GetInt16(7);
            //                prProcessId = dr.IsDBNull(8) ? 0 : dr.GetInt16(8);
            //            }
            //            dr.Close();
            //        }

            //        if (!string.IsNullOrEmpty(alParams))
            //        {
            //            string[] times = alParams.Split(separator: ',', options: StringSplitOptions.RemoveEmptyEntries);
            //            if (times.Length == 3)
            //            {
            //                if (!int.TryParse(times[0], out idleTime))
            //                    idleTime = 0;

            //                if (!int.TryParse(times[1], out timeoutTime))
            //                    timeoutTime = 0;

            //                if (!int.TryParse(times[2], out pingTime))
            //                    pingTime = 0;
            //            }
            //        }

            //        if (!request.LoginId.ToLower().Equals(User.SuperUser_LoginId) && !request.AppVersion.Equals(appVer))
            //        {
            //            user.UnsuccessfulMsg = appVer;
            //            user.LoginStatus = EnumLoginStatus.VersionMismatch;
            //        }

            //        #endregion

            //        if (user.LoginStatus != EnumLoginStatus.VersionMismatch)
            //        {
            //            #region Read User data using authentication data

            //            string commandText;
            //            if (!checkPwd)
            //            {
            //                commandText = SQLParser.MakeSQL("SELECT UserId, UserName, Status, MobileNo, EmailAddress, AuthReqAtlogin, AuthMethod,"
            //                  + " AuthKey, AppId, AccessStatus, NeverExpire, LastPasswords, LastPassChgDate, ExpireDate, ThemeName, SchemeName, MenuLayout,"
            //                  + " IsLocked, NextLoginTime, DBOnStartup, DAMultiLogin, ViewOwnTaskOnly, EmployeeId, LoginID, EmployeeCode FROM Users WHERE LoginID=%s", request.LoginId);
            //            }
            //            else
            //            {
            //                commandText = SQLParser.MakeSQL("SELECT UserId, UserName, Status, MobileNo, EmailAddress, AuthReqAtlogin, AuthMethod,"
            //                  + " AuthKey, AppId, AccessStatus, NeverExpire, LastPasswords, LastPassChgDate, ExpireDate, ThemeName, SchemeName, MenuLayout,"
            //                  + " IsLocked, NextLoginTime, DBOnStartup, DAMultiLogin, ViewOwnTaskOnly, EmployeeId, LoginID, EmployeeCode FROM Users"
            //                  + " WHERE (LoginID=%s OR MobileNo=%s OR EmailAddress=%s) AND Password=%s", request.LoginId, request.LoginId, request.LoginId, password);
            //            }

            //            using (IDataReader dr = tc.ExecuteReader(commandText: commandText))
            //            {
            //                if (dr.Read())
            //                {
            //                    user = new User
            //                    {
            //                        Id = dr.GetInt32(0),
            //                        UserName = dr.GetString(1),
            //                        Status = (EnumStatus)dr.GetInt16(2),
            //                        MobileNo = dr.GetString(3),
            //                        EmailAddress = dr.GetString(4),
            //                        AuthRequiredAtLogin = !dr.IsDBNull(5) && dr.GetInt16(5) > 0,
            //                        AuthMethod = (EnumAuthenticationMethod)dr.GetInt16(6),
            //                        AuthKey = dr.IsDBNull(7) ? string.Empty : dr.GetString(7),
            //                        AppId = dr.IsDBNull(8) ? string.Empty : dr.GetString(8),
            //                        AccessStatus = (EnumAccessStatus)dr.GetInt16(9),
            //                        NeverExpires = !dr.IsDBNull(10) && dr.GetInt16(10) > 0,
            //                        LastPasswords = dr.IsDBNull(11) ? string.Empty : dr.GetString(11),
            //                        LastPassChgDate = dr.IsDBNull(12) ? null : dr.GetDateTime(12),
            //                        ExpireDate = dr.IsDBNull(13) ? null : dr.GetDateTime(13),
            //                        ThemeName = dr.IsDBNull(14) ? "yellow" : dr.GetString(14),
            //                        SchemeName = dr.IsDBNull(15) ? "dark" : dr.GetString(15),
            //                        MenuLayout = dr.IsDBNull(16) ? "static" : dr.GetString(16),
            //                        IsLocked = !dr.IsDBNull(17) && dr.GetInt16(17) > 0,
            //                        NextLoginTime = dr.IsDBNull(18) ? null : dr.GetDateTime(18),
            //                        DbOnStartup = dr.GetInt16(19) != 0,
            //                        DisallowMultiLogin = dr.GetInt16(20) != 0,
            //                        ViewOwnTaskOnly = dr.GetInt16(21) != 0,
            //                        EmployeeId = dr.IsDBNull(22) ? null : dr.GetInt32(22),
            //                        LoginId = dr.GetString(23),
            //                        EmployeeCode = dr.IsDBNull(24) ? string.Empty : dr.GetString(24),

            //                        TeamSpaceIds = [],
            //                        IdleTime = idleTime,
            //                        PingTime = pingTime,
            //                        SystemDate = sysDate,
            //                        TimeoutTime = timeoutTime,
            //                        PrProcessId = prProcessId,
            //                        BmProcessId = bmProcessId,
            //                        BatchEnabled = batchEnabled,
            //                        LoginStatus = EnumLoginStatus.Success
            //                    };
            //                }
            //                dr.Close();

            //                user.MinReportDate = marMonths <= 0 ? new DateTime(year: 2015, month: 1, day: 1, hour: 0, minute: 0, second: 0, kind: DateTimeKind.Local) : sysDate.AddMonths(-1 * marMonths);
            //            }

            //            #endregion

            //            #region If the user was locked, try set set unlock if Time expired

            //            if (!request.LoginId.ToLower().Equals(User.SuperUser_LoginId) && user.IsLocked)
            //            {
            //                int isSuccessful = 0;
            //                SqlParameter[] p =
            //                [
            //                    SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: request.LoginId, size: 30),
            //                    SqlHelperExtension.CreateInParam(pName: "@LockTime", pType: SqlDbType.Int, pValue: lockTime),
            //                    SqlHelperExtension.CreateOutParam(pName: "@IsSuccessful", pType: SqlDbType.Int, pValue: isSuccessful),
            //                ];
            //                _ = tc.ExecuteNonQuerySp(spName: "dbo.DoUnlockUser", parameterValues: p);
            //                if (p[2] != null && p[2].Value != null && p[2].Value != DBNull.Value)
            //                    isSuccessful = Convert.ToInt32(p[2].Value);

            //                if (isSuccessful == 1)
            //                    user.IsLocked = false;
            //            }

            //            #endregion

            //            #region Keep log for unauthrise access and Set user lock if exceeds max try

            //            if (!request.LoginId.ToLower().Equals(User.SuperUser_LoginId) && maxTryCount > 0 && user.LoginStatus == EnumLoginStatus.Unsuccessful)
            //            {
            //                int remainsTry = 0;
            //                DateTime? nextLoginTime = null;
            //                string tryLoginInfo = $"{request.LoginId}~{password}~13";
            //                SqlParameter[] p =
            //                [
            //                    SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: request.LoginId, size: 30),
            //                    SqlHelperExtension.CreateInParam(pName: "@TryLoginInfo", pType: SqlDbType.VarChar, pValue: tryLoginInfo, size: 100),
            //                    SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
            //                    SqlHelperExtension.CreateInParam(pName: "@MaxTryCount", pType: SqlDbType.SmallInt, pValue: maxTryCount),
            //                    SqlHelperExtension.CreateInParam(pName: "@LockTime", pType: SqlDbType.Int, pValue: lockTime),
            //                    SqlHelperExtension.CreateOutParam(pName: "@RemainingTry", pType: SqlDbType.SmallInt, pValue: remainsTry),
            //                    SqlHelperExtension.CreateOutParam(pName: "@NextLoginTime", pType: SqlDbType.DateTime, pValue: nextLoginTime),
            //                ];
            //                _ = tc.ExecuteNonQuerySp(spName: "dbo.LogUnauthorizeAccess", parameterValues: p);
            //                if (p[5] != null && p[5].Value != null && p[5].Value != DBNull.Value)
            //                    remainsTry = Convert.ToInt32(p[5].Value);

            //                if (p[6] != null && p[6].Value != null && p[6].Value != DBNull.Value)
            //                    nextLoginTime = Convert.ToDateTime(p[6].Value);

            //                if (remainsTry <= 0)
            //                {
            //                    user.IsLocked = true;
            //                    if (lockTime <= 0)
            //                    {
            //                        user.NextLoginTime = nextLoginTime;
            //                        user.UnsuccessfulMsg = "Please contact with Head office to Unlock";
            //                    }
            //                    else
            //                    {
            //                        user.NextLoginTime = nextLoginTime;
            //                        user.UnsuccessfulMsg = $"You can Login after {user.NextLoginTime:dd-MMM-yyyy H:mm:ss}";
            //                    }
            //                }
            //                else
            //                {
            //                    user.UnsuccessfulMsg = $"{remainsTry} More attempt{(remainsTry > 1 ? "s" : "")} remaining";
            //                }
            //            }

            //            #endregion

            //            #region Generate and Save Otp if Otp is enabled and send thru SMS/Email

            //            if (user.LoginStatus == EnumLoginStatus.Success && user.Status == EnumStatus.Authorized && (user.AuthMethod == EnumAuthenticationMethod.Email || user.AuthMethod == EnumAuthenticationMethod.MobileSMS))
            //            {
            //                string secretKey = $"{user.Id}~{user.LoginId}";
            //                secretKey = secretKey.EncodeAsBase32String(addPadding: false);
            //                user.AuthValue = TOtpService.GetCurrentPIN(secretKey: secretKey);
            //                SetAuthValue(tc: tc, authValue: user.AuthValue, validMinutes: 5, userId: user.Id);
            //            }

            //            #endregion

            //            #region If login successful and user is active read module id for this user 

            //            if (user.LoginStatus == EnumLoginStatus.Success && user.Status == EnumStatus.Authorized && !user.IsLocked)
            //            {
            //                int logId = 0;
            //                DateTime? logoutTime = null;
            //                SqlParameter[] p =
            //                [
            //                    SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.Id),
            //                    SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
            //                    SqlHelperExtension.CreateInParam(pName: "@AppId", pType: SqlDbType.VarChar, pValue: request.AppId, size: 250),
            //                    SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 30),
            //                    SqlHelperExtension.CreateInParam(pName: "@LockTime", pType: SqlDbType.Int, pValue: lockTime),
            //                    SqlHelperExtension.CreateInParam(pName: "@AttendanceLogin", pType: SqlDbType.Int, pValue: request.AttendanceLogin? 1:0),
            //                    SqlHelperExtension.CreateInParam(pName: "@LocalIp", pType: SqlDbType.VarChar, pValue: request.IpAddress, size: 20),
            //                    SqlHelperExtension.CreateInParam(pName: "@MacAddress", pType: SqlDbType.VarChar, pValue: request.MacAddress, size: 30),
            //                    SqlHelperExtension.CreateInParam(pName: "@HostName", pType: SqlDbType.VarChar, pValue: request.HostName, size: 100),
            //                    SqlHelperExtension.CreateInParam(pName: "@LoginRemarks", pType: SqlDbType.VarChar, pValue: request.LoginRemarks, size: 50)
            //                ];
            //                using (IDataReader dr = tc.ExecuteReaderSp(spName: "dbo.GetPermissionKeys", parameterValues: p))
            //                {
            //                    user.ModuleIds = [];
            //                    while (dr.Read())
            //                    {
            //                        string moduleId = dr.GetString(0);
            //                        user.ModuleIds.Add(moduleId);

            //                        logId = dr.GetInt32(1);

            //                        if (dr.GetInt16(2) != 0)    //Alow add
            //                        {
            //                            user.ModuleIds.Add($"{moduleId}_1");
            //                        }
            //                        if (dr.GetInt16(3) != 0)    //Alow edit
            //                        {
            //                            user.ModuleIds.Add($"{moduleId}_2");
            //                        }

            //                        if (dr.GetInt16(4) != 0)    //Allow Delete
            //                        {
            //                            user.ModuleIds.Add($"{moduleId}_3");
            //                        }

            //                        logoutTime = dr.IsDBNull(5) ? null : dr.GetDateTime(5);
            //                    }
            //                    dr.Close();
            //                }
            //                user.LogId = logId;
            //                user.LogoutTime = logoutTime;

            //                //Read User TeamSpace Ids
            //                using (IDataReader dr = tc.ExecuteReader("SELECT TeamSpaceId FROM TeamSpaceUsers WHERE UserId=%n", user.Id))
            //                {
            //                    while (dr.Read())
            //                    {
            //                        user.TeamSpaceIds.Add(dr.GetInt32(0));
            //                    }
            //                    dr.Close();
            //                }

            //                //Pending Notification count
            //                user.NotificationCount = GetPendingNotifCount(tc: tc, userId: user.Id);
            //            }

            //            #endregion
            //        }

            //        tc.End();
            //    }
            //    catch (Exception ie)
            //    {
            //        tc?.HandleError();

            //        throw DBCustomError.GenerateCustomError(ie);
            //    }
            //}
            //catch (Exception e)
            //{
            //    throw new InvalidOperationException(e.Message, e);
            //}

            return user;
        }


        public async Task<AppAuthUserResponse> LoginAsync(MobileAppAuthRequest request, string ipAddress)
        {
            AppAuthUserResponse response = null;
            try
            {
                string password = EncryptPassword(password: request.Password);
                User user = null;
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    DateTime sysDate = DateTime.Today.Date;
                    string appVer = string.Empty, alParams = string.Empty;
                    int maxTryCount = 5, lockTime = 1;

                    bool initialPassword = false;

                    #region Read User data using authentication data

                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.NVarChar, pValue: request.LoginId)
                    ];

                    using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetUserByLogin", parameterValues: p))
                    {
                        if (dr.Read())
                        {
                            user = new User();
                            {
                                user.UserId = dr.GetInt32(0);
                                user.Password = dr.GetString(1);
                                user.LoginId = dr.GetString(2);
                                user.UserName = dr.GetString(3);
                                user.Status = (EnumStatus)dr.GetInt32(4);
                                user.MobileNo = dr.GetString(5);
                                user.EmailAddress = dr.GetString(6);
                                user.AuthKey = dr.GetString(7);
                                user.AuthValue = dr.GetString(8);
                                user.IsLocked = dr.GetBoolean(9);
                                user.LoginStatus = EnumLoginStatus.Success;
                                user.UserRoleType = (UserRoleTypeEnum)dr.GetInt32(10);
                                user.EmployeeNumber = dr.GetString(11);
                                user.DesignationCode = dr.GetString(12);
                                user.DesignationName = dr.GetString(13);                
                            };

                            initialPassword = dr.GetInt32(14) > 0;
                        }
                        dr.Close();
                    }

                    if (user is null)
                    {
                        tc.End();
                        response = new();
                        response.IsAuthorized = true;
                        return response;
                    }

                    if (!password.Equals(user.Password))
                    {
                        tc.End();
                        response = new();
                        response.IsAuthorized = false;
                        response.LoginStatus = EnumLoginStatus.Unsuccessful;
                        return response;
                    }

                    #endregion

                    #region If the user was locked, try set set unlock if Time expired

                    if (!request.LoginId.ToLower().Equals(User.SuperUser_LoginId) && user.IsLocked)
                    {
                        int isSuccessful = 0;
                        p =
                        [
                            SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: request.LoginId, size: 30),
                                SqlHelperExtension.CreateInParam(pName: "@LockTime", pType: SqlDbType.Int, pValue: lockTime),
                                SqlHelperExtension.CreateOutParam(pName: "@IsSuccessful", pType: SqlDbType.Int, pValue: isSuccessful),
                            ];
                        _ = tc.ExecuteNonQuerySp(spName: "dbo.DoUnlockUser", parameterValues: p);
                        if (p[2] != null && p[2].Value != null && p[2].Value != DBNull.Value)
                            isSuccessful = Convert.ToInt32(p[2].Value);

                        if (isSuccessful == 1)
                            user.IsLocked = false;
                    }

                    #endregion

                    #region Keep log for unauthrise access and Set user lock if exceeds max try

                    if (!request.LoginId.ToLower().Equals(User.SuperUser_LoginId) && maxTryCount > 0 && user.LoginStatus == EnumLoginStatus.Unsuccessful)
                    {
                        int remainsTry = 0;
                        DateTime? nextLoginTime = null;
                        string tryLoginInfo = $"{request.LoginId}~{password}~13";
                         p =
                        [
                                SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: request.LoginId, size: 30),
                                SqlHelperExtension.CreateInParam(pName: "@TryLoginInfo", pType: SqlDbType.VarChar, pValue: tryLoginInfo, size: 100),
                                SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                                SqlHelperExtension.CreateInParam(pName: "@MaxTryCount", pType: SqlDbType.SmallInt, pValue: maxTryCount),
                                SqlHelperExtension.CreateInParam(pName: "@LockTime", pType: SqlDbType.Int, pValue: lockTime),
                                SqlHelperExtension.CreateOutParam(pName: "@RemainingTry", pType: SqlDbType.SmallInt, pValue: remainsTry),
                                SqlHelperExtension.CreateOutParam(pName: "@NextLoginTime", pType: SqlDbType.DateTime, pValue: nextLoginTime),
                        ];
                        _ = tc.ExecuteNonQuerySp(spName: "dbo.LogUnauthorizeAccess", parameterValues: p);
                        if (p[5] != null && p[5].Value != null && p[5].Value != DBNull.Value)
                            remainsTry = Convert.ToInt32(p[5].Value);

                        if (p[6] != null && p[6].Value != null && p[6].Value != DBNull.Value)
                            nextLoginTime = Convert.ToDateTime(p[6].Value);

                        if (remainsTry <= 0)
                        {
                            user.IsLocked = true;
                            if (lockTime <= 0)
                            {
                                user.NextLoginTime = nextLoginTime;
                                user.UnsuccessfulMsg = "Please contact with Head office to Unlock";
                            }
                            else
                            {
                                user.NextLoginTime = nextLoginTime;
                                user.UnsuccessfulMsg = $"You can Login after {user.NextLoginTime:dd-MMM-yyyy H:mm:ss}";
                            }
                        }
                        else
                        {
                            user.UnsuccessfulMsg = $"{remainsTry} More attempt{(remainsTry > 1 ? "s" : "")} remaining";
                        }
                    }

                    #endregion


                    #region If login successful and user is active read module id for this user 

                    if (user.LoginStatus == EnumLoginStatus.Success && user.Status == EnumStatus.Authorized && !user.IsLocked)
                    {
                        p =
                        [
                                SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.UserId),
                                SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 30),
                                SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                                SqlHelperExtension.CreateInParam(pName: "@LoginTime", pType: SqlDbType.DateTime, pValue: DateTime.Now),
                                SqlHelperExtension.CreateInParam(pName: "@LogoutTime", pType: SqlDbType.DateTime, pValue: null)
                        ];

                        _ = tc.ExecuteNonQuerySp(spName: "dbo.SaveAccessLog", parameterValues: p);
                    }

                    #endregion

                    response = new()
                    {
                        UserId = user.UserId,
                        Name = user.UserName,
                        LoginId = user.LoginId,
                        Email = user.EmailAddress,
                        MobileNumber = user.MobileNo,
                        IsLocked = user.IsLocked,
                        UserRole = user.UserRoleType.Value,
                        LoginStatus = user.LoginStatus,
                        IsAuthorized = EnumStatus.Authorized == user.Status,
                        EmployeeNumber = user.EmployeeNumber,
                        DesignationCode = user.DesignationCode,
                        DesignationName = user.DesignationName,
                        IsInitialPassword = initialPassword
                    };

                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        public async Task<User> IntegrationLoginAsync(IntegrationLoginRequest request, string ipAddress, bool checkPwd)
        {
            User user = null;
            try
            {
                string password = EncryptPassword(password: request.Password);

                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    DateTime sysDate = DateTime.Today.Date;
                    string appVer = string.Empty, alParams = string.Empty;
                    int maxTryCount = 5, lockTime = 1;

                    #region Read User data using authentication data

                    string commandText = SQLParser.MakeSQL("SELECT UserId,Password,LoginId, UserName, Status, ISNULL(MobileNo,'')MobileNo ,ISNULL( EmailAddress,'')EmailAddress,"
                          + " ISNULL(AuthKey,'') AuthKey, ISNULL(AuthValue,'')AuthValue, IsLocked FROM Users WHERE LoginID=%s and UserPlatformId=1 ", request.LoginId);

                    using (IDataReader dr = tc.ExecuteReader(commandText: commandText))
                    {
                        if (dr.Read())
                        {
                            user = new User
                            {
                                UserId = dr.GetInt32(0),
                                Password = dr.GetString(1),
                                LoginId = dr.GetString(2),
                                UserName = dr.GetString(3),
                                Status = (EnumStatus)dr.GetInt32(4),
                                MobileNo = dr.GetString(5),
                                EmailAddress = dr.GetString(6),
                                AuthKey = dr.GetString(7),
                                AuthValue = dr.GetString(8),
                                IsLocked = dr.GetBoolean(9),
                                LoginStatus = EnumLoginStatus.Success
                            };
                        }
                        dr.Close();
                    }

                    if (user is null)
                    {
                        tc.End();
                        throw new InvalidOperationException($"User not found with login: {request.LoginId}");
                    }

                    if (!password.Equals(user.Password))
                    {
                        tc.End();
                        throw new InvalidOperationException($"Password mismatch for login: {request.LoginId}");
                    }


                    #endregion

                    #region If the user was locked, try set set unlock if Time expired

                    if (!request.LoginId.ToLower().Equals(User.SuperUser_LoginId) && user.IsLocked)
                    {
                        int isSuccessful = 0;
                        SqlParameter[] p =
                        [
                            SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: request.LoginId, size: 30),
                                SqlHelperExtension.CreateInParam(pName: "@LockTime", pType: SqlDbType.Int, pValue: lockTime),
                                SqlHelperExtension.CreateOutParam(pName: "@IsSuccessful", pType: SqlDbType.Int, pValue: isSuccessful),
                            ];
                        _ = tc.ExecuteNonQuerySp(spName: "dbo.DoUnlockUser", parameterValues: p);
                        if (p[2] != null && p[2].Value != null && p[2].Value != DBNull.Value)
                            isSuccessful = Convert.ToInt32(p[2].Value);

                        if (isSuccessful == 1)
                            user.IsLocked = false;
                    }

                    #endregion

                    #region Keep log for unauthrise access and Set user lock if exceeds max try

                    if (!request.LoginId.ToLower().Equals(User.SuperUser_LoginId) && maxTryCount > 0 && user.LoginStatus == EnumLoginStatus.Unsuccessful)
                    {
                        int remainsTry = 0;
                        DateTime? nextLoginTime = null;
                        string tryLoginInfo = $"{request.LoginId}~{password}~13";
                        SqlParameter[] p =
                        [
                                SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: request.LoginId, size: 30),
                                SqlHelperExtension.CreateInParam(pName: "@TryLoginInfo", pType: SqlDbType.VarChar, pValue: tryLoginInfo, size: 100),
                                SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                                SqlHelperExtension.CreateInParam(pName: "@MaxTryCount", pType: SqlDbType.SmallInt, pValue: maxTryCount),
                                SqlHelperExtension.CreateInParam(pName: "@LockTime", pType: SqlDbType.Int, pValue: lockTime),
                                SqlHelperExtension.CreateOutParam(pName: "@RemainingTry", pType: SqlDbType.SmallInt, pValue: remainsTry),
                                SqlHelperExtension.CreateOutParam(pName: "@NextLoginTime", pType: SqlDbType.DateTime, pValue: nextLoginTime),
                        ];
                        _ = tc.ExecuteNonQuerySp(spName: "dbo.LogUnauthorizeAccess", parameterValues: p);
                        if (p[5] != null && p[5].Value != null && p[5].Value != DBNull.Value)
                            remainsTry = Convert.ToInt32(p[5].Value);

                        if (p[6] != null && p[6].Value != null && p[6].Value != DBNull.Value)
                            nextLoginTime = Convert.ToDateTime(p[6].Value);

                        if (remainsTry <= 0)
                        {
                            user.IsLocked = true;
                            if (lockTime <= 0)
                            {
                                user.NextLoginTime = nextLoginTime;
                                user.UnsuccessfulMsg = "Please contact with Head office to Unlock";
                            }
                            else
                            {
                                user.NextLoginTime = nextLoginTime;
                                user.UnsuccessfulMsg = $"You can Login after {user.NextLoginTime:dd-MMM-yyyy H:mm:ss}";
                            }
                        }
                        else
                        {
                            user.UnsuccessfulMsg = $"{remainsTry} More attempt{(remainsTry > 1 ? "s" : "")} remaining";
                        }
                    }

                    #endregion


                    #region If login successful and user is active read module id for this user 

                    if (user.LoginStatus == EnumLoginStatus.Success && user.Status == EnumStatus.Authorized && !user.IsLocked)
                    {
                        SqlParameter[] p =
                        [
                                SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.UserId),
                                SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 30),
                                SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),  
                                SqlHelperExtension.CreateInParam(pName: "@LoginTime", pType: SqlDbType.DateTime, pValue: DateTime.Now),
                                SqlHelperExtension.CreateInParam(pName: "@LogoutTime", pType: SqlDbType.DateTime, pValue: null)
                        ];

                        _ =  tc.ExecuteNonQuerySp(spName: "dbo.SaveAccessLog", parameterValues: p);
                    }

                    #endregion

                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return user;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task<int> LoadNotificationCountAsync(int userId)
        {
            int value = 0;
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    value = GetPendingNotifCount(tc: tc, userId: userId);
                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return value;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="accountId"></param>
        /// <returns></returns>
        public async Task<FindAccountResponse> FindAccountAsync(string accountId)
        {
            FindAccountResponse response = new();
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    using (IDataReader dr = tc.ExecuteReader("SELECT UserID, EmailAddress, MobileNo FROM Users WHERE LoginID=%s OR EmailAddress=%s OR MobileNo=%s", accountId, accountId, accountId))
                    {
                        if (dr.Read())
                        {
                            response.UserId = dr.GetInt32(0);
                            response.EmailAddress = dr.GetString(1);
                            response.PhoneNo = dr.GetString(2);
                            response.Value = true;
                        }
                        dr.Close();
                    }
                    tc.End();

                    if (!response.Value)
                    {
                        response.ReturnMessage.Add($"[{accountId}] is not a valid Login Id/Email address/Mobile number.");
                    }
                    else
                    {
                        if (response.PhoneNo.Length > 8)
                            response.PhoneNoMasked = string.Concat(response.PhoneNo.AsSpan(0, response.PhoneNo.Length - 8), "*****", response.PhoneNo.AsSpan(response.PhoneNo.Length - 3, 3));

                        int idx = response.EmailAddress.IndexOf('@');
                        if (idx != -1)
                        {
                            string firstPart = response.EmailAddress[..idx];
                            if (firstPart.Length >= 5)
                                firstPart = string.Concat(firstPart.AsSpan(0, firstPart.Length - 5), "*****");

                            response.EmailAddressMasked = string.Concat(firstPart, response.EmailAddress.AsSpan(idx, response.EmailAddress.Length - idx));
                        }
                    }

                    response.ReturnStatus = 200;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="authValue"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<bool> ValidateAuthValueAsync(string authValue, int userId)
        {
            bool returnValue = false;
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@AuthValue", pType: SqlDbType.VarChar, pValue: authValue, size: 10),
                        SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                        SqlHelperExtension.CreateOutParam(pName: "@Valid", pType: SqlDbType.SmallInt, pValue: 0)
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.ValidateAuthValue", parameterValues: p);
                    if (p[2].Value != null && p[2].Value != DBNull.Value)
                        returnValue = Convert.ToInt16(p[2].Value) > 0;

                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="user"></param>
        /// <param name="ipAddress"></param>
        /// <param name="createdBy"></param>
        /// <returns></returns>
        public async Task<bool> AddUserAsync(NewUserRequest user, string ipAddress, int createdBy)
        {
            bool returnValue;
            try
            {
                #region Password Policy if Does not change password on next login

                if (user.AccessStatus != EnumAccessStatus.FirstTime)
                {
                    ReadPwdParams(userId: 0, loginId: user.LoginId, enfStgPwd: out bool enfStgPwd, minLen: out short minLen, maxLen: out short maxLen, reservedWords: out string reservedWords);
                    ValidatePwdPolicies(password: user.Password, minLen: minLen, maxLen: maxLen, enfStgPwd: enfStgPwd, reservedWords: reservedWords);
                }

                #endregion

                int userId = 0;
                user.Password = EncryptPassword(password: user.Password);

                if (!string.IsNullOrEmpty(user.EmailAddress))
                    user.EmailAddress = user.EmailAddress.Replace(" ", "");

                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 30),
                        SqlHelperExtension.CreateInParam(pName: "@UserName", pType: SqlDbType.VarChar, pValue: user.UserName, size: 75),
                        SqlHelperExtension.CreateInParam(pName: "@Designation", pType: SqlDbType.VarChar, pValue: user.Designation, size: 50),
                        SqlHelperExtension.CreateInParam(pName: "@Password", pType: SqlDbType.VarChar, pValue: user.Password, size: 75),
                        SqlHelperExtension.CreateInParam(pName: "@MobileNo", pType: SqlDbType.VarChar, pValue: user.MobileNo, size: 15),
                        SqlHelperExtension.CreateInParam(pName: "@EmailAddress", pType: SqlDbType.VarChar, pValue: user.EmailAddress, size: 100),
                        SqlHelperExtension.CreateInParam(pName: "@EmployeeId", pType: SqlDbType.Int, pValue: DataReader.GetNullValue(user.EmployeeId)),
                        SqlHelperExtension.CreateInParam(pName: "@EmployeeCode", pType: SqlDbType.VarChar, pValue: DataReader.GetNullValue(user.EmployeeCode), size: 15),
                        SqlHelperExtension.CreateInParam(pName: "@AuthReqAtlogin", pType: SqlDbType.SmallInt, pValue: user.AuthReqAtlogin ? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@AuthMethod", pType: SqlDbType.SmallInt, pValue: user.AuthMethod),
                        SqlHelperExtension.CreateInParam(pName: "@DBOnStartup", pType: SqlDbType.SmallInt, pValue: user.DbOnStartup ? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@Status", pType: SqlDbType.SmallInt, pValue: user.Status),
                        SqlHelperExtension.CreateInParam(pName: "@SeqId", pType: SqlDbType.SmallInt, pValue: user.SeqId),
                        SqlHelperExtension.CreateInParam(pName: "@CanUseAtnSys", pType: SqlDbType.SmallInt, pValue: user.CanUseAttendanceSystem? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@ViewToAll", pType: SqlDbType.SmallInt, pValue: user.ViewToAll? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@AccessStatus", pType: SqlDbType.SmallInt, pValue: user.AccessStatus),
                        SqlHelperExtension.CreateInParam(pName: "@NeverExpire", pType: SqlDbType.SmallInt, pValue: user.NeverExpire ? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@DAMultiLogin", pType: SqlDbType.SmallInt, pValue: user.DisallowMultiLogin ? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                        SqlHelperExtension.CreateInParam(pName: "@CreatedBy", pType: SqlDbType.Int, pValue: createdBy),
                        SqlHelperExtension.CreateOutParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.InsertUser", parameterValues: p);
                    userId = (p[20]?.Value != null && p[20]?.Value != DBNull.Value) ? Convert.ToInt32(p[20].Value) : 0;

                    if (user.GroupIds != null && user.GroupIds.Count > 0 && userId > 0)
                    {
                        List<int> ownerGroupsIds = [];
                        if (createdBy == User.SuperUser_Id)
                        {
                            ownerGroupsIds.AddRange(user.GroupIds);
                        }
                        else
                        {
                            using IDataReader dr = tc.ExecuteReader("SELECT GroupId FROM Groups WHERE (CreatedBy=%n OR ViewToAll=1)", createdBy);
                            while (dr.Read())
                            {
                                ownerGroupsIds.Add(dr.GetInt32(0));
                            }
                            dr.Close();
                        }

                        StringBuilder sb = new();
                        foreach (int groupId in user.GroupIds)
                        {
                            if (!ownerGroupsIds.Contains(groupId))
                                continue;

                            sb.Append($"INSERT INTO UserGroups(UserId,GroupId) VALUES({userId},{groupId});");
                        }
                        _ = tc.ExecuteNonQuery(commandText: sb.ToString());
                    }

                    tc.End();

                    #region Copy File

                    try
                    {
                        string destFileName = System.IO.Path.Combine(_settings.ProfileImageFolder, $"{userId}.png");
                        string sourceFileName = System.IO.Path.Combine(_settings.ProfileImageFolder, "default-user.png");
                        if (System.IO.File.Exists(path: sourceFileName))
                        {
                            System.IO.File.Copy(sourceFileName: sourceFileName, destFileName: destFileName);
                        }
                    }
                    catch
                    {
                        //Do nothing                       
                    }

                    #endregion

                    returnValue = true;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="user"></param>
        /// <param name="ipAddress"></param>
        /// <param name="modifiedBy"></param>
        /// <returns></returns>
        public async Task<bool> EditUserAsync(UserRequest user, string ipAddress, int modifiedBy)
        {
            bool returnValue = false;
            try
            {
                if (!string.IsNullOrEmpty(user.EmailAddress))
                    user.EmailAddress = user.EmailAddress.Replace(" ", "");

                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 30),
                        SqlHelperExtension.CreateInParam(pName: "@UserName", pType: SqlDbType.VarChar, pValue: user.UserName, size: 75),
                        SqlHelperExtension.CreateInParam(pName: "@Designation", pType: SqlDbType.VarChar, pValue: user.Designation, size: 50),
                        SqlHelperExtension.CreateInParam(pName: "@MobileNo", pType: SqlDbType.VarChar, pValue: user.MobileNo, size: 15),
                        SqlHelperExtension.CreateInParam(pName: "@EmailAddress", pType: SqlDbType.VarChar, pValue: user.EmailAddress ?? string.Empty, size: 100),
                        SqlHelperExtension.CreateInParam(pName: "@AuthReqAtlogin", pType: SqlDbType.SmallInt, pValue: user.AuthReqAtlogin ? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@AuthMethod", pType: SqlDbType.SmallInt, pValue: user.AuthMethod),
                        SqlHelperExtension.CreateInParam(pName: "@AuthKey", pType: SqlDbType.VarChar, pValue: user.AuthKey, size: 100),
                        SqlHelperExtension.CreateInParam(pName: "@DBOnStartup", pType: SqlDbType.SmallInt, pValue: user.DbOnStartup ? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@Status", pType: SqlDbType.SmallInt, pValue: user.Status),
                        SqlHelperExtension.CreateInParam(pName: "@SeqId", pType: SqlDbType.SmallInt, pValue: user.SeqId),
                        SqlHelperExtension.CreateInParam(pName: "@CanUseAtnSys", pType: SqlDbType.SmallInt, pValue: user.CanUseAttendanceSystem? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@ViewToAll", pType: SqlDbType.SmallInt, pValue: user.ViewToAll? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@AccessStatus", pType: SqlDbType.SmallInt, pValue: user.AccessStatus),
                        SqlHelperExtension.CreateInParam(pName: "@NeverExpire", pType: SqlDbType.SmallInt, pValue: user.NeverExpire ? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@DAMultiLogin", pType: SqlDbType.SmallInt, pValue: user.DisallowMultiLogin ? 1 : 0),
                        SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                        SqlHelperExtension.CreateInParam(pName: "@ModifiedBy", pType: SqlDbType.Int, pValue: modifiedBy),
                        SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.UserId)
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.UpdateUser", parameterValues: p);

                    if (user.GroupIds != null && user.GroupIds.Count > 0 && user.UserId > 0)
                    {
                        List<int> ownerGroupsIds = [];
                        if (modifiedBy == User.SuperUser_Id)
                        {
                            ownerGroupsIds.AddRange(user.GroupIds);
                        }
                        else
                        {
                            using IDataReader dr = tc.ExecuteReader("SELECT GroupId FROM Groups WHERE (CreatedBy=%n OR ViewToAll=1)", modifiedBy);
                            while (dr.Read())
                            {
                                ownerGroupsIds.Add(dr.GetInt32(0));
                            }
                            dr.Close();
                        }

                        StringBuilder sb = new();
                        foreach (int groupId in user.GroupIds)
                        {
                            if (!ownerGroupsIds.Contains(groupId))
                                continue;

                            sb.Append($"INSERT INTO UserGroups(UserId,GroupId) VALUES({user.UserId},{groupId});");
                        }
                        _ = tc.ExecuteNonQuery(commandText: sb.ToString());
                    }

                    tc.End();

                    #region Copy File

                    try
                    {
                        string destFileName = System.IO.Path.Combine(_settings.ProfileImageFolder, $"{user.UserId}.png");
                        string sourceFileName = System.IO.Path.Combine(_settings.ProfileImageFolder, "default-user.png");
                        if (System.IO.File.Exists(path: sourceFileName) && !System.IO.File.Exists(path: destFileName))
                        {
                            System.IO.File.Copy(sourceFileName: sourceFileName, destFileName: destFileName);
                        }
                    }
                    catch
                    {
                        //Do nothing                       
                    }

                    #endregion

                    returnValue = true;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="deletedBy"></param>
        /// <returns></returns>
        public async Task<bool> DeleteUserAsync(int userId, int deletedBy)
        {
            bool returnValue;
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@DeletedBy", pType: SqlDbType.Int, pValue: deletedBy),
                        SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId)
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.DeleteUser", parameterValues: p);

                    tc.End();

                    #region Delete File

                    try
                    {
                        string fileSpec = System.IO.Path.Combine(_settings.ProfileImageFolder, $"{userId}.png");
                        if (System.IO.File.Exists(path: fileSpec))
                            System.IO.File.Delete(path: fileSpec);
                    }
                    catch
                    {
                        //Do nothing                       
                    }

                    #endregion
                    returnValue = true;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="loginId"></param>
        /// <param name="unlockedBy"></param>
        /// <returns></returns>
        public async Task<bool> UnlockUserAsync(int userId, string loginId, int unlockedBy)
        {
            bool returnValue;
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                        SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: loginId, size: 30),
                        SqlHelperExtension.CreateInParam(pName: "@UnlockedBy", pType: SqlDbType.Int, pValue: unlockedBy)
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.UnlockUser", parameterValues: p);

                    tc.End();

                    returnValue = true;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="newPassword"></param>
        /// <param name="ipAddress"></param>
        /// <param name="changedBy"></param>
        /// <returns></returns>
        public async Task<bool> ResetPasswordAsync(int userId, string newPassword, string ipAddress, int changedBy)
        {
            bool returnValue = false;

            try
            {
                try
                {
                    newPassword = EncryptPassword(password: newPassword);
                    using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                    try
                    {
                        SqlParameter[] p =
                        [
                            SqlHelperExtension.CreateInParam(pName: "@NewPwd", pType: SqlDbType.VarChar, pValue: newPassword, size: 75),
                            SqlHelperExtension.CreateInParam(pName: "@LastPwds", pType: SqlDbType.VarChar, pValue: string.Empty, size: 700),
                            SqlHelperExtension.CreateInParam(pName: "@LastPwdChgDate", pType: SqlDbType.DateTime, pValue: DateTime.Now),
                            SqlHelperExtension.CreateInParam(pName: "@PwdExpireDate", pType: SqlDbType.DateTime, pValue: DateTime.Now),
                            SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                            SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                            SqlHelperExtension.CreateInParam(pName: "@ChangedBy", pType: SqlDbType.Int, pValue: changedBy),
                            SqlHelperExtension.CreateInParam(pName: "@ResetPwd", pType: SqlDbType.SmallInt, pValue: 1)
                        ];
                        _ = tc.ExecuteNonQuerySp(spName: "dbo.UpdateUserPassword", parameterValues: p);

                        tc.End();

                        returnValue = true;
                    }
                    catch (Exception ie)
                    {
                        tc?.HandleError();

                        throw DBCustomError.GenerateCustomError(ie);
                    }
                }
                catch (Exception e)
                {
                    throw new InvalidOperationException(e.Message, e);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="newPassword"></param>
        /// <param name="ipAddress"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public async Task<bool> SendPasswordAsync(int userId, string newPassword, string ipAddress)
        {
            bool returnValue = false;

            try
            {
                newPassword = EncryptPassword(password: newPassword);
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@NewPwd", pType: SqlDbType.VarChar, pValue: newPassword, size: 75),
                        SqlHelperExtension.CreateInParam(pName: "LastPwds", pType: SqlDbType.VarChar, pValue: string.Empty, size: 700),
                        SqlHelperExtension.CreateInParam(pName: "@LastPwdChgDate", pType: SqlDbType.DateTime, pValue: DateTime.Now),
                        SqlHelperExtension.CreateInParam(pName: "@PwdExpireDate", pType: SqlDbType.DateTime, pValue: DateTime.Now),
                        SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                        SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                        SqlHelperExtension.CreateInParam(pName: "@ChangedBy", pType: SqlDbType.Int, pValue: userId),
                        SqlHelperExtension.CreateInParam(pName: "@ResetPwd", pType: SqlDbType.SmallInt, pValue: 1)
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.UpdateUserPassword", parameterValues: p);

                    tc.End();

                    returnValue = true;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="newPassword"></param>
        /// <param name="oldPassword"></param>
        /// <param name="ipAddress"></param>
        /// <param name="changedBy"></param>
        /// <returns></returns>
        public async Task<bool> ChangePasswordAsync(int userId, string oldPassword, string newPassword, string ipAddress, int changedBy)
        {
            bool returnValue = false;

            try
            {
                bool loginIdCantPwd = false;
                DateTime? lastPassChgDate = null;
                List<string> actPwdHistories = [];
                Queue<string> pwdHistories = new();
                EnumAccessStatus accessStatus = EnumAccessStatus.LoggedOut;
                short pwdMinLen = 0, pwdMaxLen = 0, daLastPwds = 0, expiryDays = 0;
                bool neverExpire = false, enfStgPwd = false, prvntAtckPwdReuse = false;
                string loginId = string.Empty, password = string.Empty, lastPasswords = string.Empty, reservedWords = string.Empty;

                #region Reading Data from DB

                try
                {
                    using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                    try
                    {
                        using IDataReader dr = tc.ExecuteReader("SELECT A.LoginId, A.Password, A.LastPasswords, A.LastPassChgDate, A.NeverExpire, A.AccessStatus,"
                          + " B.EnfStgPwd, B.PwdMinLen, B.PwdMaxLen, B.ExpiryDays, B.DALastPwds, B.PwdRsvdWords, B.loginIdCantPwd, B.PrvntAtckPwdReuse"
                          + " FROM Users A LEFT OUTER JOIN ThisSystem B ON A.UserID=A.UserID WHERE A.UserID=%n", userId);

                        if (dr.Read())
                        {
                            loginId = dr.GetString(0);
                            password = dr.GetString(1);
                            lastPasswords = dr.IsDBNull(2) ? string.Empty : dr.GetString(2);
                            lastPassChgDate = dr.IsDBNull(3) ? null : dr.GetDateTime(3);
                            neverExpire = !dr.IsDBNull(4) && dr.GetInt16(4) > 0;
                            accessStatus = (EnumAccessStatus)dr.GetInt16(5);
                            enfStgPwd = !dr.IsDBNull(6) && dr.GetInt16(6) > 0;
                            pwdMinLen = dr.IsDBNull(7) ? Convert.ToInt16(0) : dr.GetInt16(7);
                            pwdMaxLen = dr.IsDBNull(8) ? Convert.ToInt16(30) : dr.GetInt16(8);
                            expiryDays = dr.IsDBNull(9) ? Convert.ToInt16(0) : dr.GetInt16(9);
                            daLastPwds = dr.IsDBNull(10) ? Convert.ToInt16(0) : dr.GetInt16(10);
                            reservedWords = dr.IsDBNull(11) ? string.Empty : dr.GetString(11);
                            loginIdCantPwd = !dr.IsDBNull(12) && dr.GetInt16(12) != 0;
                            prvntAtckPwdReuse = !dr.IsDBNull(13) && dr.GetInt16(13) != 0;
                        }
                        dr.Close();
                        tc.End();

                        if (loginIdCantPwd && !string.IsNullOrEmpty(loginId))
                        {
                            reservedWords = string.IsNullOrEmpty(reservedWords) ? loginId : $"{reservedWords},{loginId}";
                        }
                    }
                    catch (Exception ie)
                    {
                        tc?.HandleError();

                        throw DBCustomError.GenerateCustomError(ie);
                    }
                }
                catch (Exception e)
                {
                    throw new InvalidOperationException(e.Message, e);
                }
                #endregion

                #region Validation

                oldPassword = EncryptPassword(password: oldPassword);
                if (!oldPassword.Equals(password))
                    throw new InvalidOperationException("Old Password does not match with system.");

                #region Password Histories

                if (!string.IsNullOrEmpty(lastPasswords) && !string.IsNullOrWhiteSpace(lastPasswords))
                {
                    string[] pwds = lastPasswords.Split(',');
                    foreach (string pwd in pwds)
                    {
                        pwdHistories.Enqueue(pwd);
                    }
                }

                #endregion

                #region Actual Password Histories

                string[] pwdsHists = [.. pwdHistories];
                if (daLastPwds > 0 && pwdsHists.Length > 0)
                {
                    for (int idx = pwdsHists.Length - 1; idx >= 0; idx--)
                    {
                        if (!actPwdHistories.Contains(pwdsHists[idx]) && actPwdHistories.Count <= daLastPwds)
                            actPwdHistories.Add(pwdsHists[idx]);
                    }
                }

                #endregion

                if (lastPassChgDate.HasValue && prvntAtckPwdReuse && accessStatus != EnumAccessStatus.FirstTime && lastPassChgDate.Value.Date >= DateTime.Today.Date)
                {
                    throw new InvalidOperationException("Password should be at least one day old to change again.");
                }
                CheckPasswordHistory(password: newPassword, minLen: pwdMinLen, maxLen: pwdMaxLen, enfStgPwd: enfStgPwd, reservedWords: reservedWords, daLastPwds: daLastPwds, pwdHistories: pwdHistories, actPwdHistories: actPwdHistories);

                #endregion

                #region Updateing Data

                if (pwdHistories.ToArray().Length > 0)
                    lastPasswords = string.Join(",", [.. pwdHistories]);

                DateTime? expireDate = null;
                lastPassChgDate = DateTime.Now;
                if (!neverExpire && expiryDays > 0)
                    expireDate = DateTime.Now.AddDays(expiryDays);

                try
                {
                    newPassword = EncryptPassword(password: newPassword);
                    using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                    try
                    {
                        SqlParameter[] p =
                        [
                            SqlHelperExtension.CreateInParam(pName: "@NewPwd", pType: SqlDbType.VarChar, pValue: newPassword, size: 75),
                            SqlHelperExtension.CreateInParam(pName: "@LastPwds", pType: SqlDbType.VarChar, pValue: lastPasswords, size: 700),
                            SqlHelperExtension.CreateInParam(pName: "@LastPwdChgDate", pType: SqlDbType.DateTime, pValue: lastPassChgDate),
                            SqlHelperExtension.CreateInParam(pName: "@PwdExpireDate", pType: SqlDbType.DateTime, pValue: expireDate),
                            SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                            SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                            SqlHelperExtension.CreateInParam(pName: "@ChangedBy", pType: SqlDbType.Int, pValue: changedBy),
                            SqlHelperExtension.CreateInParam(pName: "@ResetPwd", pType: SqlDbType.SmallInt, pValue: 0)
                        ];
                        _ = tc.ExecuteNonQuerySp(spName: "dbo.UpdateUserPassword", parameterValues: p);

                        tc.End();

                        returnValue = true;
                    }
                    catch (Exception ie)
                    {
                        tc?.HandleError();

                        throw DBCustomError.GenerateCustomError(ie);
                    }
                }
                catch (Exception e)
                {
                    throw new InvalidOperationException(e.Message, e);
                }

                #endregion
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="menuLayout"></param>
        /// <param name="themeName"></param>
        /// <param name="schemeName"></param>
        /// <returns></returns>
        public async Task<bool> UpdateMyThemeAsync(int userId, string menuLayout, string themeName, string schemeName)
        {
            bool returnValue = false;

            try
            {
                try
                {
                    using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                    try
                    {
                        SqlParameter[] p =
                        [
                            SqlHelperExtension.CreateInParam(pName: "@MenuLayout", pType: SqlDbType.VarChar, pValue: menuLayout, size: 15),
                            SqlHelperExtension.CreateInParam(pName: "@ThemeName", pType: SqlDbType.VarChar, pValue: themeName, size: 15),
                            SqlHelperExtension.CreateInParam(pName: "@SchemeName", pType: SqlDbType.VarChar, pValue: schemeName, size: 10),
                            SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId)
                        ];
                        _ = tc.ExecuteNonQuerySp(spName: "dbo.UpdateMyTheme", parameterValues: p);

                        tc.End();

                        returnValue = true;
                    }
                    catch (Exception ie)
                    {
                        tc?.HandleError();

                        throw DBCustomError.GenerateCustomError(ie);
                    }
                }
                catch (Exception e)
                {
                    throw new InvalidOperationException(e.Message, e);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="address"></param>
        /// <param name="contactNo"></param>
        /// <param name="modifiedBy"></param>
        /// <param name="emplyeeId"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task<bool> UpdateMyInfoAsync(string address, string contactNo, int modifiedBy, int emplyeeId)
        {
            bool returnValue = false;

            try
            {
                try
                {
                    using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                    try
                    {
                        SqlParameter[] p =
                        [
                            SqlHelperExtension.CreateInParam(pName: "@Address", pType: SqlDbType.VarChar, pValue: address, size: 120),
                            SqlHelperExtension.CreateInParam(pName: "@ContactNo", pType: SqlDbType.VarChar, pValue: contactNo, size: 25),
                            SqlHelperExtension.CreateInParam(pName: "@ModifiedBy", pType: SqlDbType.Int, pValue: modifiedBy),
                            SqlHelperExtension.CreateInParam(pName: "@EmployeeId", pType: SqlDbType.Int, pValue: emplyeeId)
                        ];
                        _ = tc.ExecuteNonQuerySp(spName: "dbo.UpdateMyInfo", parameterValues: p);

                        tc.End();

                        returnValue = true;
                    }
                    catch (Exception ie)
                    {
                        tc?.HandleError();

                        throw DBCustomError.GenerateCustomError(ie);
                    }
                }
                catch (Exception e)
                {
                    throw new InvalidOperationException(e.Message, e);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="clientType"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task<UserAttributesResponse> GetAttributesAsync(int userId, int clientType)
        {
            UserAttributesResponse response = new() { ReturnStatus = 200 };
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    using (IDataReader dr = tc.ExecuteReader("SELECT LoginId FROM Users WHERE UserId=%n", userId))
                    {
                        if (dr.Read())
                        {
                            response = new UserAttributesResponse
                            {
                                UserId = userId,
                                LoginId = dr.GetString(0),
                                ReturnStatus = 200,
                                UkIds = [],
                            };
                        }
                        dr.Close();
                    }

                    #region Client & Projects

                    if (response.UserId > 0)
                    {
                        using IDataReader dr = tc.ExecuteReader($"SELECT ProjectId, ClientId FROM UserClients WHERE UserId=%n AND ProjectId IS {(clientType == 1 ? "NOT " : "")}NULL", response.UserId);
                        while (dr.Read())
                        {
                            string ukId = $"{(dr.IsDBNull(0) ? 0 : dr.GetInt32(0))}|{dr.GetInt32(1)}";
                            response.UkIds.Add(ukId);
                        }
                        dr.Close();
                    }

                    response.HasSetup = response.UkIds.Count > 0;

                    #endregion

                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="clientType"></param>
        /// <param name="ipAddress"></param>
        /// <param name="attributeSetBy"></param>
        /// <param name="ukIds"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>        


        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="clientType"></param>
        /// <param name="deletedBy"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task<bool> DeleteAttributesAsync(int userId, int clientType, int deletedBy)
        {
            bool returnValue;
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                        SqlHelperExtension.CreateInParam(pName: "@ClientType", pType: SqlDbType.SmallInt, pValue: clientType),
                        SqlHelperExtension.CreateInParam(pName: "@DeletedBy", pType: SqlDbType.Int, pValue: deletedBy)
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.DeleteUserAttributes", parameterValues: p);

                    tc.End();

                    returnValue = true;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        //public async Task<UserSearchResponse> GetUsersAsync(UserSearchRequest request, int userId)
        //{
        //    UserSearchResponse response = new();
        //    try
        //    {
        //        string andClause = string.Empty;
        //        if (!string.IsNullOrEmpty(request.Criteria))
        //        {
        //            string criteria = request.Criteria.Replace("'", "''");
        //            andClause = $" AND (LoginID='{criteria}' OR MobileNo='{criteria}' OR EmailAddress='{criteria}' OR UserName LIKE '%{criteria}%' OR Designation LIKE '%{criteria}%')";
        //        }

        //        if (request.Status > 0)
        //            andClause += SQLParser.MakeSQL(" AND Status=%n", request.Status);

        //        if (request.CheckOwner)
        //            andClause += SQLParser.MakeSQL(" AND (CreatedBy=%n OR ViewToAll=1)", userId);

        //        string sortField = request.SortField switch
        //        {
        //            "userName" => "UserName",
        //            "designation" => "Designation",
        //            "mobileNo" => "MobileNo",
        //            "emailAddress" => "EmailAddress",
        //            "statusDetail" => "Status",
        //            "loginId" => "LoginId",
        //            _ => "SeqId, LoginId"
        //        };

        //        string sortOrder = request.SortOrder switch
        //        {
        //            "desc" => "DESC",
        //            _ => "ASC",
        //        };

        //        string commandText = request.Skip + request.PageSize <= 0 ?
        //          SQLParser.MakeSQL("SELECT UserId, LoginId, UserName, Designation, MobileNo, EmailAddress, Status, SeqId, IsLocked, CanUseAtnSys, Password,"
        //            + " COUNT(*) OVER() AS TotalRows FROM Users WHERE UserId!=-9%q ORDER BY %q %q", andClause, sortField, sortOrder)
        //          : SQLParser.MakeSQL("SELECT A.UserId, A.LoginId, A.UserName, A.Designation, A.MobileNo, A.EmailAddress, A.Status, A.SeqId, A.IsLocked,"
        //            + " A.CanUseAtnSys, A.Password, A.TotalRows FROM(SELECT UserId, LoginId, UserName, Designation, MobileNo, EmailAddress, Status, SeqId,"
        //            + " IsLocked, CanUseAtnSys, Password, ROW_NUMBER() OVER(ORDER BY %q %q) AS RN, COUNT(*) OVER() AS TotalRows FROM Users WHERE UserId!=-9%q) A"
        //            + " WHERE A.RN>%n AND A.RN<=%n", sortField, sortOrder, andClause, request.Skip, request.Skip + request.PageSize);

        //        using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
        //        try
        //        {
        //            int totalRows = 0;
        //            using IDataReader dr = tc.ExecuteReader(commandText: commandText);
        //            while (dr.Read())
        //            {
        //                UserGetResponse item = new()
        //                {
        //                    UserId = dr.GetInt32(0),
        //                    LoginId = dr.GetString(1),
        //                    UserName = dr.GetString(2),
        //                    Designation = dr.GetString(3),
        //                    MobileNo = dr.GetString(4),
        //                    EmailAddress = dr.GetString(5),
        //                    Status = (EnumStatus)dr.GetInt16(6),
        //                    SeqId = dr.GetInt16(7),
        //                    IsLocked = !dr.IsDBNull(8) && dr.GetInt16(8) != 0,
        //                    CanUseAttendanceSystem = !dr.IsDBNull(9) && dr.GetInt16(9) != 0,
        //                };
        //                totalRows = dr.GetInt32(11);

        //            }
        //            dr.Close();

        //            response.TotalRows = totalRows;

        //            tc.End();
        //            response.ReturnStatus = 200;
        //        }
        //        catch (Exception ie)
        //        {
        //            tc?.HandleError();

        //            throw DBCustomError.GenerateCustomError(ie);
        //        }
        //    }
        //    catch (Exception e)
        //    {
        //        throw new InvalidOperationException(e.Message, e);
        //    }

        //    return response;
        //}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="teamSpaceIds"></param>
        /// <param name="projectId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task<UserBasicInfoResponse> GetUsersByTeamSpaceAsync(string teamSpaceIds, int projectId, int userId)
        {
            UserBasicInfoResponse response = new();
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    string andClause = string.Empty;
                    if (userId > 0)
                    {
                        andClause = $" AND UserID={userId}";
                    }
                    else
                    {
                        if (!string.IsNullOrEmpty(teamSpaceIds))
                        {
                            andClause = $" AND UserID IN(SELECT UserId FROM TeamSpaceUsers WHERE TeamSpaceId IN({teamSpaceIds}))";
                        }

                        if (projectId > 0)
                        {
                            andClause += $" AND UserID IN(SELECT UserId FROM TeamSpaceUserProjects WHERE ProjectId={projectId})";
                        }
                    }

                    string commandText = $"SELECT UserID, LoginID, UserName, Designation FROM Users WHERE UserID!={User.SuperUser_Id} AND Status=8{andClause} ORDER BY SeqId, UserName";
                    using IDataReader dr = tc.ExecuteReader(commandText: commandText);
                    while (dr.Read())
                    {
                        UserBasicInfo item = new()
                        {
                            UserId = dr.GetInt32(0),
                            LoginId = dr.GetString(1),
                            UserName = dr.GetString(2),
                            Designation = dr.GetString(3),
                        };

                        response.Value.Add(item);
                    }
                    dr.Close();

                    tc.End();
                    response.ReturnStatus = 200;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="applyFilter"></param>
        /// <param name="teamSpaceIds"></param>
        /// <param name="projectId"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task<UserBasicInfoResponse> GetUsersBasicAsync(bool applyFilter, string teamSpaceIds, int projectId)
        {
            UserBasicInfoResponse response = new();
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    string commandText;
                    if (applyFilter)
                    {
                        if (!string.IsNullOrEmpty(teamSpaceIds))
                        {
                            commandText = $"SELECT UserID, LoginID, UserName, Designation FROM Users WHERE UserID!={User.SuperUser_Id}"
                                + $" AND Status=8 AND UserID IN(SELECT UserId FROM TeamSpaceUsers WHERE TeamSpaceId IN({teamSpaceIds}))";
                        }
                        else
                        {
                            commandText = $"SELECT UserID, LoginID, UserName, Designation FROM Users WHERE UserID!={User.SuperUser_Id} AND Status=8";
                        }

                        if (projectId > 0)
                        {
                            commandText += $" AND UserID IN(SELECT UserId FROM UserClients WHERE ProjectID={projectId})";
                        }
                    }
                    else
                    {
                        commandText = $"SELECT UserID, LoginID, UserName, Designation FROM Users WHERE UserID!={User.SuperUser_Id} AND Status=8";
                    }
                    commandText += " ORDER BY SeqId, UserName";

                    using IDataReader dr = tc.ExecuteReader(commandText: commandText);
                    while (dr.Read())
                    {
                        UserBasicInfo item = new()
                        {
                            UserId = dr.GetInt32(0),
                            LoginId = dr.GetString(1),
                            UserName = dr.GetString(2),
                            Designation = dr.GetString(3),
                        };

                        response.Value.Add(item);
                    }
                    dr.Close();

                    tc.End();
                    response.ReturnStatus = 200;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="createdBy"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public async Task<UserForceLogoutResponse> GetForceLogoutUsersAsync(int createdBy)
        {
            UserForceLogoutResponse response = new();
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    string commandText;
                    if (createdBy == User.SuperUser_Id)
                        commandText = $"SELECT UserID, LoginID, UserName FROM Users WHERE UserID!={User.SuperUser_Id} AND Status=8 ORDER BY LoginID";
                    else
                        commandText = $"SELECT UserID, LoginID, UserName FROM Users WHERE (CreatedBy={createdBy} OR ViewToAll=1) AND Status=8 ORDER BY LoginID";

                    using IDataReader dr = tc.ExecuteReader(commandText: commandText);
                    while (dr.Read())
                    {
                        UserForceLogout item = new()
                        {
                            UserId = dr.GetInt32(0),
                            LoginId = dr.GetString(1),
                            UserName = dr.GetString(2)
                        };

                        response.Value.Add(item);
                    }
                    dr.Close();

                    tc.End();
                    response.ReturnStatus = 200;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task<UserBasicInfoResponse> GetAttendanceUsersAsync(int userId)
        {
            UserBasicInfoResponse response = new();
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    string andClause = userId > 0 ? $" AND UserID={userId}" : string.Empty;
                    using IDataReader dr = tc.ExecuteReader(commandText: $"SELECT UserID, LoginID, UserName, Designation FROM Users WHERE EmployeeId IS NOT NULL AND Status=8{andClause} ORDER BY SeqId, UserName");
                    while (dr.Read())
                    {
                        UserBasicInfo item = new()
                        {
                            UserId = dr.GetInt32(0),
                            LoginId = dr.GetString(1),
                            UserName = dr.GetString(2),
                            Designation = dr.GetString(3),
                        };

                        response.Value.Add(item);
                    }
                    dr.Close();

                    tc.End();
                    response.ReturnStatus = 200;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userIds"></param>
        /// <param name="ipAddress"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public async Task<bool> ForceLogoutNowAsync(List<int> userIds, string ipAddress)
        {
            bool returnValue;
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@UserIDs", pType: SqlDbType.VarChar, pValue: string.Join(",", userIds.ToArray()), size: 8000),
                        SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20)
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.DoForceLogout", parameterValues: p);

                    tc.End();

                    returnValue = true;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<UserGetResponse> GetUserAsync(int userId)
        {
            UserGetResponse response = new() { ReturnStatus = 200 };
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    using (IDataReader dr = tc.ExecuteReader("SELECT LoginId, UserName, Designation, MobileNo, EmailAddress, IsLocked, Status,"
                      + " AccessStatus, NeverExpire, AuthReqAtlogin, AuthMethod, DBOnStartup, DAMultiLogin, EmployeeCode, SeqId, CanUseAtnSys, ViewToAll"
                      + " FROM Users WHERE UserId=%n", userId))
                    {
                        if (dr.Read())
                        {
                            response = new UserGetResponse
                            {
                                UserId = userId,
                                LoginId = dr.GetString(0),
                                UserName = dr.GetString(1),
                                Designation = dr.GetString(2),
                                MobileNo = dr.GetString(3),
                                EmailAddress = dr.GetString(4),
                                IsLocked = !dr.IsDBNull(5) && dr.GetInt16(5) > 0,
                                Status = (EnumStatus)dr.GetInt16(6),
                                AccessStatus = (EnumAccessStatus)dr.GetInt16(7),
                                NeverExpire = !dr.IsDBNull(8) && dr.GetInt16(8) != 0,
                                AuthRequiredAtLogin = !dr.IsDBNull(9) && dr.GetInt16(9) != 0,
                                AuthMethod = (EnumAuthenticationMethod)dr.GetInt16(10),
                                DbOnStartup = !dr.IsDBNull(11) && dr.GetInt16(11) != 0,
                                DisallowMultiLogin = !dr.IsDBNull(12) && dr.GetInt16(12) != 0,
                                EmployeeCode = dr.IsDBNull(13) ? string.Empty : dr.GetString(13),
                                SeqId = dr.GetInt16(14),
                                CanUseAttendanceSystem = !dr.IsDBNull(15) && dr.GetInt16(15) != 0,
                                ViewToAll = !dr.IsDBNull(16) && dr.GetInt16(16) != 0,
                                ReturnStatus = 200
                            };
                        }
                        dr.Close();
                    }

                    response.GroupIds = [];
                    using (IDataReader dr = tc.ExecuteReader("SELECT GroupId FROM UserGroups WHERE UserID=%n", userId))
                    {
                        while (dr.Read())
                        {
                            response.GroupIds.Add(dr.GetInt32(0));
                        }
                        dr.Close();
                    }

                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<UserProfileResponse> GetUserProfileAsync(int userId)
        {
            UserProfileResponse response = new() { ReturnStatus = 200 };
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    int employeeId = 0;
                    using (IDataReader dr = tc.ExecuteReader("SELECT LoginId, UserName, Designation, LastPassChgDate, ExpireDate, EmployeeId FROM Users WHERE UserId=%n", userId))
                    {
                        if (dr.Read())
                        {
                            response = new UserProfileResponse
                            {
                                UserId = userId,
                                LoginId = dr.GetString(0),
                                UserName = dr.GetString(1),
                                Designation = dr.GetString(2),
                                PwdLastChangedTime = dr.IsDBNull(3) ? null : dr.GetDateTime(3),
                                NextPwdDate = dr.IsDBNull(4) ? null : dr.GetDateTime(4),
                                ReturnStatus = 200
                            };

                            employeeId = dr.IsDBNull(5) ? 0 : dr.GetInt32(5);
                        }
                        dr.Close();
                    }

                    if (employeeId > 0)
                    {
                        using IDataReader dr = tc.ExecuteReader("SELECT Address, ContactNo FROM Employees WHERE EmployeeId=%n", employeeId);
                        if (dr.Read())
                        {
                            response.Address = dr.GetString(0);
                            response.ContactNo = dr.GetString(1);
                        }
                        dr.Close();
                    }

                    response.LoginHistories = [];
                    using (IDataReader dr = tc.ExecuteReader("SELECT TOP 10 LoginIPAddress, LoginTime, LogoutIPAddress, LogoutTime"
                        + " FROM AccessLog WHERE UserID=%n ORDER BY LoginTime DESC", userId))
                    {
                        int slNo = 0;
                        while (dr.Read())
                        {
                            slNo++;
                            LoginHistory item = new()
                            {
                                SlNo = slNo,
                                LoginIp = dr.GetString(0),
                                LoginTime = dr.GetDateTime(1),
                                LogoutIp = dr.IsDBNull(2) ? string.Empty : dr.GetString(2),
                                LogoutTime = dr.IsDBNull(3) ? null : dr.GetDateTime(3),
                            };
                            response.LoginHistories.Add(item: item);
                        }
                        dr.Close();
                    }

                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<MenuResponse> GetUserPermissionsAsync(int userId)
        {
            MenuResponse response = new() { Item = new() { Items = [] } };
            MenuItem pi = new() { Label = "User Profile", Icon = "fa fa-user-md", RouterLink = string.Empty, Items = [] };
            pi.Items.Add(new() { Label = "My Profile", Icon = "fa fa-user", RouterLink = "/myprofile" });
            pi.Items.Add(new() { Label = "Change My Password", Icon = "fa fa-unlock", RouterLink = "/changemypwd" });
            pi.Items.Add(new() { Label = "Change My Theme", Icon = "fa fa-user", RouterLink = "/changemytheme" });
            pi.Items.Add(new() { Label = "WhatsApp Message", Icon = "fa fa-whatsapp", RouterLink = "/sendwhatsappmsg" });
            pi.Items.Add(new() { Label = "Access Log", Icon = "fa fa-list-alt", RouterLink = "/accesslog" });
            response.Item.Items.Add(pi);

            List<GroupPermission> pmns = [];
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    SqlParameter[] p = [SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId)];
                    using IDataReader dr = tc.ExecuteReaderSp(spName: "dbo.GetUserPermissions", parameterValues: p);
                    while (dr.Read())
                    {
                        GroupPermission item = new()
                        {
                            ModuleId = dr.GetString(0),
                            AllowSelect = dr.GetInt16(1) != 0,
                            AllowAdd = dr.GetInt16(2) != 0,
                            AllowEdit = dr.GetInt16(3) != 0,
                            AllowDelete = dr.GetInt16(4) != 0
                        };
                        pmns.Add(item);
                    }
                    dr.Close();

                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }

                #region Making Hierarchy

                List<GroupPermission> items = [];
                List<GroupPermission> fullMenu = GlobalFunctions.BuildMenu();
                IEnumerable<GroupPermission> data = fullMenu.Where(x => (string.IsNullOrEmpty(x.ParentId) || string.IsNullOrWhiteSpace(x.ParentId)) && x.Visible && pmns.Where(y => y.AllowSelect).Select(z => z.ModuleId).Contains(x.ModuleId));
                foreach (GroupPermission datum in data)
                {
                    MakeHierarchy(parent: datum, data: fullMenu, pmns: pmns);
                }
                items.AddRange(data);

                if (_menuSettings?.MenuItems?.Count > 0)
                {
                    foreach (GroupPermission src in items)
                    {
                        MenuItem dst = src.Copy();
                        dst.Label = _menuSettings.GetItem(src.ModuleId, src.ModuleName).Value;
                        response.Item.Items.Add(dst);

                        CopyHierarchy(src: src, dst: dst);
                    }
                }
                else
                {
                    foreach (GroupPermission src in items)
                    {
                        MenuItem dst = src.Copy();
                        response.Item.Items.Add(dst);

                        CopyHierarchy(src: src, dst: dst);
                    }
                }
                items.Clear();

                #endregion
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="canViewLeave"></param>
        /// <param name="canViewLate"></param>
        /// <param name="canViewClientVisit"></param>
        /// <param name="canViewHomeOffice"></param>
        /// <param name="viewAll"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
      
        /// <summary>
        /// 
        /// </summary>
        /// <param name="poId"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>


        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="id"></param>
        /// <param name="documentOf"></param>
        /// <param name="orgFileName"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public async Task<bool> UploadDocumentAsync(int userId, int id, int documentOf, string orgFileName, string fileName)
        {
            bool returnValue = false;
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@UserID", pType: SqlDbType.Int, pValue: userId),
                        SqlHelperExtension.CreateInParam(pName: "@Id", pType: SqlDbType.Int, pValue: id),
                        SqlHelperExtension.CreateInParam(pName: "@DocumentOf", pType: SqlDbType.SmallInt, pValue: documentOf),
                        SqlHelperExtension.CreateInParam(pName: "@OrgFileName", pType: SqlDbType.VarChar, pValue: orgFileName, size: 200),
                        SqlHelperExtension.CreateInParam(pName: "@FileName", pType: SqlDbType.VarChar, pValue: fileName, size: 100)
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.UploadDocument", parameterValues: p);

                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ipAddress"></param>
        /// <param name="userId"></param>
        /// <param name="logId"></param>
        /// <param name="attendanceLogout"></param>
        /// <param name="loginId"></param>
        /// <param name="localIp"></param>
        /// <param name="macAddress"></param>
        /// <param name="hostName"></param>
        /// <param name="logoutRemarks"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task<bool> LogoutAsync(string ipAddress, int userId, int logId, bool attendanceLogout, string loginId, string localIp, string macAddress, string hostName, string logoutRemarks)
        {
            bool returnValue = false;
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p;
                    #region Attendance Logout and Valid Ip Address

                    if (attendanceLogout && userId != User.SuperUser_Id)
                    {
                        string errMsg = string.Empty;
                        p =
                        [
                            SqlHelperExtension.CreateInParam(pName: "@UserID", pType: SqlDbType.Int, pValue: userId),
                            SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: loginId, size: 30),
                            SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: localIp, size: 20),
                            SqlHelperExtension.CreateInParam(pName: "@MacAddress", pType: SqlDbType.VarChar, pValue: macAddress, size: 30),
                            SqlHelperExtension.CreateInParam(pName: "@HostName", pType: SqlDbType.VarChar, pValue: hostName, size: 100),
                            SqlHelperExtension.CreateInParam(pName: "@LogoutRemarks", pType: SqlDbType.VarChar, pValue: logoutRemarks, size: 50),
                            SqlHelperExtension.CreateOutParam(pName: "@ErrMsg", pType: SqlDbType.VarChar, pValue: errMsg, size: 300)
                        ];
                        _ = tc.ExecuteNonQuerySp(spName: "dbo.LogoutFromAttendance", parameterValues: p);

                        errMsg = (p[6] == null || p[6].Value == null || p[6].Value == DBNull.Value) ? string.Empty : Convert.ToString(p[6].Value);
                        if (!string.IsNullOrEmpty(errMsg))
                        {
                            throw new InvalidOperationException(errMsg);
                        }
                    }

                    #endregion

                    //Keep access history
                    p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@UserID", pType: SqlDbType.Int, pValue: userId),
                        SqlHelperExtension.CreateInParam(pName: "@Status", pType: SqlDbType.TinyInt, pValue: 3),
                        SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                        SqlHelperExtension.CreateInParam(pName: "@LogID", pType: SqlDbType.Int, pValue: logId),
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.CreateAccessLog", parameterValues: p);

                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return returnValue;
        }

        public async Task<AuthLogoutResponse> LogoutAsync(AuthLogoutRequest request, string ipAddress)
        {
            AuthLogoutResponse response = null;
            try
            {
                User user = null;
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    DateTime sysDate = DateTime.Today.Date;
                    string appVer = string.Empty, alParams = string.Empty;

                    #region Read User data using authentication data

                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.NVarChar, pValue: request.LoginId)
                    ];

                    using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetUserByLogin", parameterValues: p))
                    {
                        if (dr.Read())
                        {
                            user = new User();
                            {
                                user.UserId = dr.GetInt32(0);
                                user.Password = dr.GetString(1);
                                user.LoginId = dr.GetString(2);
                                user.UserName = dr.GetString(3);
                                user.Status = (EnumStatus)dr.GetInt32(4);
                                user.MobileNo = dr.GetString(5);
                                user.EmailAddress = dr.GetString(6);
                                user.AuthKey = dr.GetString(7);
                                user.AuthValue = dr.GetString(8);
                                user.IsLocked = dr.GetBoolean(9);
                                user.LoginStatus = EnumLoginStatus.Success;
                                user.UserRoleType = (UserRoleTypeEnum)dr.GetInt32(10);
                                user.EmployeeNumber = dr.GetString(11);
                                user.DesignationCode = dr.GetString(12);
                                user.DesignationName = dr.GetString(13);
                            }
                            ;
                        }
                        dr.Close();
                    }

                    if (user is null)
                    {
                        tc.End();
                        throw new InvalidOperationException($"User not found with login: {request.LoginId}");
                    }


                    #endregion


                    #region If login successful and user is active read module id for this user 

                    if (user.LoginStatus == EnumLoginStatus.Success && user.Status == EnumStatus.Authorized && !user.IsLocked)
                    {
                        p =
                        [
                                SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.UserId),
                                SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 30),
                                SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                                SqlHelperExtension.CreateInParam(pName: "@LoginTime", pType: SqlDbType.DateTime, pValue: null),
                                SqlHelperExtension.CreateInParam(pName: "@LogoutTime", pType: SqlDbType.DateTime, pValue: DateTime.Now)
                        ];

                        _ = tc.ExecuteNonQuerySp(spName: "dbo.SaveAccessLog", parameterValues: p);
                    }

                    #endregion

                    await _refreshTokenService.InvalidRefreshTokenAsync(tc, user.UserId);

                    response = new()
                    {
                        UserId = user.UserId,
                        LoginId = user.LoginId,
                        IsSuccess = true
                    };
                    
                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        public async Task<AppChangePasswordResponse> ChangePasswordAsync(AppUserChangePasswordRequest request, string ipAddress)
        {
            AppChangePasswordResponse response = null;
            try
            {
                string password = EncryptPassword(password: request.Password);
                User user = null;
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    DateTime sysDate = DateTime.Today.Date;
                    string appVer = string.Empty, alParams = string.Empty;

                    #region Read User data using authentication data

                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.NVarChar, pValue: request.LoginId)
                    ];

                    using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetUserByLogin", parameterValues: p))
                    {
                        if (dr.Read())
                        {
                            user = new User();
                            {
                                user.UserId = dr.GetInt32(0);
                                user.Password = dr.GetString(1);
                                user.LoginId = dr.GetString(2);
                                user.UserName = dr.GetString(3);
                                user.Status = (EnumStatus)dr.GetInt32(4);
                                user.MobileNo = dr.GetString(5);
                                user.EmailAddress = dr.GetString(6);
                                user.AuthKey = dr.GetString(7);
                                user.AuthValue = dr.GetString(8);
                                user.IsLocked = dr.GetBoolean(9);
                                user.LoginStatus = EnumLoginStatus.Success;
                                user.UserRoleType = (UserRoleTypeEnum)dr.GetInt32(10);
                                user.EmployeeNumber = dr.GetString(11);
                                user.DesignationCode = dr.GetString(12);
                                user.DesignationName = dr.GetString(13);
                            }
                            ;
                        }
                        dr.Close();
                    }

                    if (user is null)
                    {
                        tc.End();
                        throw new InvalidOperationException($"User not found with login: {request.LoginId}");
                    }

                    if (!password.Equals(user.Password))
                    {
                        tc.End();
                        throw new InvalidOperationException($"Password mismatch for login: {request.LoginId}");
                    }


                    #endregion

                    string newPassword = EncryptPassword(request.ConfirmPassword);


                    p =
                      [
                         SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.UserId),
                         SqlHelperExtension.CreateInParam(pName: "@Password", pType: SqlDbType.NVarChar, pValue: newPassword, size: 50),
                         SqlHelperExtension.CreateInParam(pName: "@LastPassword", pType: SqlDbType.NVarChar, pValue: password, size: 50),
                         SqlHelperExtension.CreateInParam(pName: "@LastPasswordChnageDate", pType: SqlDbType.DateTime, pValue: DateTime.Now)
                      ];

                    _ = tc.ExecuteNonQuerySp(spName: "dbo.ChangePassword", parameterValues: p);

                    response = new()
                    {
                        UserId = user.UserId,
                        LoginId = user.LoginId,
                        IsSuccess = true
                    };

                    tc.End();
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="accessType"></param>
        /// <param name="loginId"></param>
        /// <param name="startDate"></param>
        /// <param name="endDate"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public async Task<AccessLogResponse> GetAccessLogAsync(int accessType, string loginId, DateTime startDate, DateTime endDate)
        {
            AccessLogResponse response = new() { Value = [] };
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    SqlParameter[] p =
                    [
                        SqlHelperExtension.CreateInParam(pName: "@AccessType", pType: SqlDbType.SmallInt, pValue: accessType),
                        SqlHelperExtension.CreateInParam(pName: "@LoginID", pType: SqlDbType.VarChar, pValue: loginId, size: 30),
                        SqlHelperExtension.CreateInParam(pName: "@StartDate", pType: SqlDbType.DateTime, pValue: startDate),
                        SqlHelperExtension.CreateInParam(pName: "@EndDate", pType: SqlDbType.DateTime, pValue: endDate),
                    ];
                    using IDataReader dr = tc.ExecuteReaderSp(spName: "dbo.GetAccessLogData", parameterValues: p);
                    while (dr.Read())
                    {
                        AccessLog item = new()
                        {
                            LoginId = dr.GetString(0),
                            LoginTime = dr.GetDateTime(1),
                            LoginIp = dr.GetString(2),
                            LogoutTime = dr.IsDBNull(3) ? null : dr.GetDateTime(3),
                            LogoutIp = dr.IsDBNull(4) ? null : dr.GetString(4)
                        };

                        response.Value.Add(item);
                    }
                    dr.Close();

                    tc.End();
                    response.ReturnStatus = 200;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="maxAuthLimit"></param>
        /// <param name="userId"></param>
        /// <param name="ipAddress"></param>
        /// <param name="savedBy"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task<bool> SaveAuthorizeLimitAsync(decimal maxAuthLimit, int userId, string ipAddress, string savedBy)
        {
            bool returnValue;
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
                try
                {
                    SqlParameter[] p =
                    [

                        SqlHelperExtension.CreateInParam(pName: "@MaxAuthLimit", pType: SqlDbType.Decimal, pValue: maxAuthLimit),
                        SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                        SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
                        SqlHelperExtension.CreateInParam(pName: "@SavedBy", pType: SqlDbType.VarChar, pValue: savedBy, size: 30)
                    ];
                    _ = tc.ExecuteNonQuerySp(spName: "dbo.SaveAuthorizeLimit", parameterValues: p);

                    tc.End();

                    returnValue = true;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }
            return returnValue;
        }

        public async Task<UserAuthorizeLimitResponse> GetAuthorizeLimitAsync(int userId)
        {
            UserAuthorizeLimitResponse response = new() { Value = 0 };
            try
            {
                using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    using IDataReader dr = tc.ExecuteReader(commandText: "SELECT LoginId, MaxAuthLimit FROM dbo.Users WHERE UserId=%n", args: userId);
                    if (dr.Read())
                    {
                        response.LoginId = dr.GetString(0);
                        response.Value = dr.IsDBNull(1) ? 0 : dr.GetDecimal(1);
                    }
                    dr.Close();

                    tc.End();
                    response.ReturnStatus = 200;
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }

            return response;
        }

        #region Private functions

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="loginId"></param>
        /// <param name="enfStgPwd"></param>
        /// <param name="minLen"></param>
        /// <param name="maxLen"></param>
        /// <param name="reservedWords"></param>
        /// <exception cref="Exception"></exception>
        private void ReadPwdParams(int userId, string loginId, out bool enfStgPwd, out short minLen, out short maxLen, out string reservedWords)
        {
            try
            {
                enfStgPwd = false;
                minLen = maxLen = 0;
                reservedWords = string.Empty;
                using TransactionContext tc = TransactionContext.Begin(_settings.DefaultConnection.ConnectionNode);
                try
                {
                    bool loginIdCantPwd = false;
                    using (IDataReader dr = tc.ExecuteReader("SELECT EnfStgPwd, PwdMinLen, PwdMaxLen, PwdRsvdWords, LoginIdCantPwd FROM ThisSystem"))
                    {
                        if (dr.Read())
                        {
                            enfStgPwd = Convert.ToBoolean(dr.GetInt16(0));
                            minLen = dr.GetInt16(1);
                            maxLen = dr.GetInt16(2);
                            reservedWords = dr.IsDBNull(3) ? string.Empty : dr.GetString(3);
                            loginIdCantPwd = !dr.IsDBNull(4) && dr.GetInt16(4) != 0;
                        }
                        dr.Close();
                    }

                    if (string.IsNullOrEmpty(loginId) && loginIdCantPwd && userId > 0)
                    {
                        using IDataReader dr = tc.ExecuteReader("SELECT LoginId FROM Users WHERE UserId=%n", userId);
                        if (dr.Read())
                        {
                            loginId = dr.IsDBNull(0) ? string.Empty : dr.GetString(0);
                        }
                        dr.Close();
                    }
                    tc.End();

                    if (loginIdCantPwd && !string.IsNullOrEmpty(loginId))
                    {
                        reservedWords = string.IsNullOrEmpty(reservedWords) ? loginId : $"{reservedWords},{loginId}";
                    }
                }
                catch (Exception ie)
                {
                    tc?.HandleError();

                    throw DBCustomError.GenerateCustomError(ie);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="password"></param>
        /// <param name="minLen"></param>
        /// <param name="maxLen"></param>
        /// <param name="enfStgPwd"></param>
        /// <param name="reservedWords"></param>
        /// <param name="daLastPwds"></param>
        /// <param name="pwdHistories"></param>
        /// <param name="actPwdHistories"></param>
        /// <exception cref="Exception"></exception>
        private void CheckPasswordHistory(string password, short minLen, short maxLen, bool enfStgPwd, string reservedWords, int daLastPwds, Queue<string> pwdHistories, List<string> actPwdHistories)
        {
            try
            {
                ValidatePwdPolicies(password: password, minLen: minLen, maxLen: maxLen, enfStgPwd: enfStgPwd, reservedWords: reservedWords);

                password = EncryptPassword(password: password);
                if (daLastPwds > 0 && actPwdHistories.Contains(password))
                    throw new InvalidOperationException($"You cannot use this password, because it was used in last {daLastPwds} passwords.");

                if (pwdHistories.Count >= 5)
                    pwdHistories.Dequeue();

                pwdHistories.Enqueue(password);
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }
        }

        /// <summary>
        /// Validates a password against specified policies, including length constraints, reserved words, and strong password requirements.
        /// </summary>
        /// <remarks>This method performs validation based on the provided policies and throws an exception if the password does not comply.
        /// Ensure that all parameters are correctly configured before calling  this method.</remarks>
        /// <param name="password">The password to validate. Cannot be null or empty.</param>
        /// <param name="minLen">The minimum allowed length for the password. Must be greater than 0.</param>
        /// <param name="maxLen">The maximum allowed length for the password. Must be greater than 0.</param>
        /// <param name="enfStgPwd">A value indicating whether strong password enforcement is enabled. If <see langword="true"/>,  the password
        /// must contain at least one uppercase letter, one lowercase letter, one number,  and one special character (!, @, #, $, %, ^, &, *, ?).</param>
        /// <param name="reservedWords">A comma-separated list of reserved words that cannot be included in the password. Cannot be null or empty.</param>
        /// <exception cref="InvalidOperationException">Thrown if the password violates any of the specified policies, including: <list type="bullet">
        /// <item><description>Contains a reserved word.</description></item> <item><description>Does not meet the minimum length requirement.</description></item> <item>
        /// <description>Exceeds the maximum length requirement.</description></item> <item><description>Fails strong password enforcement when <paramref
        /// name="enfStgPwd"/> is <see langword="true"/>.</description></item> </list></exception>
        private static void ValidatePwdPolicies(string password, short minLen, short maxLen, bool enfStgPwd, string reservedWords)
        {
            try
            {
                if (!string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(reservedWords))
                {
                    List<string> words = [.. reservedWords.Split(',')];
                    foreach (string word in words)
                    {
                        if (password.Contains(word, StringComparison.InvariantCultureIgnoreCase))
                            throw new InvalidOperationException($"[{word}] can not be used in password.");
                    }
                }

                if (minLen > 0 && password.Length < minLen)
                    throw new InvalidOperationException($"Minimum length of Password is {minLen}");

                if (maxLen > 0 && password.Length > maxLen)
                    throw new InvalidOperationException($"Maximum length of Password is {maxLen}");

                if (enfStgPwd && !Global.StringFunctions.IsStrongPassword(password))
                    throw new InvalidOperationException("Must have Upper & Lower case character, a Number and a Special character (!, @, #, $, %, ^, &, *, ?).");
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="password"></param>
        /// <returns></returns>
        private string EncryptPassword(string password)
        {
            try
            {
                string pwdSecretKey = GlobalFunctions.ConvertFromBase64String(_settings.PwdSecretKey);
                return Global.CipherFunctions.EncryptByAES(privateKey: pwdSecretKey, publicKey: pwdSecretKey, data: password);
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="tc"></param>
        /// <param name="authValue"></param>
        /// <param name="validMinutes"></param>
        /// <param name="userId"></param>
        private static void SetAuthValue(TransactionContext tc, string authValue, int validMinutes, int userId)
        {
            try
            {
                SqlParameter[] p =
                [
                    SqlHelperExtension.CreateInParam(pName: "@AuthValue", pType: SqlDbType.VarChar, pValue: authValue, size: 10),
                    SqlHelperExtension.CreateInParam(pName: "@ValidMinutes", pType: SqlDbType.Int, pValue: validMinutes),
                    SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId)
                ];
                _ = tc.ExecuteNonQuerySp(spName: "dbo.SetAuthValue", parameterValues: p);
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="tc"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        private static int GetPendingNotifCount(TransactionContext tc, int userId)
        {
            try
            {
                SqlParameter[] p =
                [
                     SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                     SqlHelperExtension.CreateOutParam(pName: "@NotifCount", pType: SqlDbType.Int, pValue: 0)
                ];
                _ = tc.ExecuteNonQuerySp(spName: "dbo.GetPendingAuthCountByUser", parameterValues: p);
                return (p[1].Value != null && p[1].Value != DBNull.Value) ? Convert.ToInt32(p[1].Value) : 0;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="data"></param>
        /// <param name="pmns"></param>
        private static void MakeHierarchy(GroupPermission parent, List<GroupPermission> data, List<GroupPermission> pmns)
        {
            try
            {
                IEnumerable<GroupPermission> children = data.Where(x => x.ParentId == parent.ModuleId && x.Visible && pmns.Where(y => y.AllowSelect).Select(z => z.ModuleId).Contains(x.ModuleId));
                parent.Children.AddRange(children);

                foreach (GroupPermission child in parent.Children)
                {
                    MakeHierarchy(parent: child, data: data, pmns: pmns);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        private void CopyHierarchy(GroupPermission src, MenuItem dst)
        {
            try
            {
                if (src.Children?.Count > 0)
                    dst.Items = [];

                foreach (GroupPermission srcChild in src.Children)
                {
                    MenuItem dstChild = srcChild.Copy();
                    dstChild.Label = _menuSettings?.GetItem(dstChild.ModuleId, dstChild.Label).Value;
                    dst.Items.Add(dstChild);

                    CopyHierarchy(src: srcChild, dst: dstChild);
                }

            }
            catch (Exception e)
            {
                throw new InvalidOperationException(e.Message, e);
            }
        }

        public Task<bool> SaveAttributesAsync(int userId, int clientType, string ipAddress, int attributeSetBy, List<string> ukIds)
        {
            throw new NotImplementedException();
        }

    
        #endregion
    }
}