using Azure.Core;
using CloudBuilder.Core.Authorization;
using CloudBuilder.Core.DatabaseAccessor;
using CloudBuilder.Core.DatabaseAccessor.Entity;
using CloudBuilder.Core.DependencyInjection.EventBuses;
using CloudBuilder.Core.DependencyInjection.Request;
using CloudBuilder.Core.Policy;
using CloudBuilder.Core.Service;
using CloudBuilder.Security.Data;
using CloudBuilder.Security.Entity;
using CloudBuilder.Security.Policy;
using CloudBuilder.Security.Publisher;
using CloudBuilder.Security.Service.Authorization;
using CloudBuilder.Security.Service.Publisher;
using DocumentFormat.OpenXml.Spreadsheet;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Runtime;
using System.Text;
using System.Text.RegularExpressions;

namespace CloudBuilder.Security.Service
{
    public class AuthService : IAuthService
    {
        private readonly IApplicationService service;
        private readonly IHttpContextAccessor httpContextAccessor;
        private readonly ILocaleMessageManager localeMessageManager;
        private readonly IContext context;
        private IRepository<SecurityUserEntity> repository;
        public AuthService(IApplicationService service, IHttpContextAccessor httpContextAccessor, ILocaleMessageManager localeMessageManager, IContext context)
        {
            this.service = service;
            this.httpContextAccessor = httpContextAccessor;
            this.localeMessageManager = localeMessageManager;
            this.context = context;
            repository = service.GetRepository<IRepository<SecurityUserEntity>>();
        }

        [AllowAnonymous]
        [UnitOfWork]
        public LoginProfile Login(LoginData data)
        {
            context.SetCulture(data.Culture);
            string certification = localeMessageManager.ComposeMessage(typeof(AuthService).Name, "certification");
            SecurityUserEntity securityUserEntity = repository.DetachedEntities.Where(x =>
                     (x.UserName == data.UserMobile || x.UserMobile == data.UserMobile)
                     && x.UserIsValidIndc == YesNoPolicy.YES).FirstOrDefault()!;

            if (securityUserEntity == null)
            {
                throw new ValidatedException(certification);
            }

            var descKey = service.Configuration[SecuritySettingsPolicy.SecuritySettings_DescKey];

            var privateKey = service.Configuration[SecuritySettingsPolicy.SecuritySettings_RsaPrivateKey];
            var rsa = new RSA(privateKey, true);

            var psd = rsa.DecodeOrNull(data.RsaUserPassword);
            if (string.IsNullOrEmpty(psd)) throw new ValidatedException(certification);
            //052636a8-b146-9efd-f5dc-0034a3c5bb34+password
            var guid = psd.Substring(0, 36);


            if (guid != data.Guid)
            {
                throw new ValidatedException(certification);
            }

            if (securityUserEntity.IsAccountsLockedIndc == YesNoPolicy.YES && securityUserEntity.AccountsLockedDatetime.HasValue
                && securityUserEntity.AccountsLockedDatetime.Value.Subtract(DateTime.Now).TotalMinutes > 0)
            {
                throw new ValidatedException(localeMessageManager.ComposeMessage("AuthService", "locked"));
            }

            data.UserPassword = psd.Substring(36, psd.Length - 36);

            if (securityUserEntity.UserPassword != data.UserPassword)
            {
                securityUserEntity.ErrorLoginCount += 1;
                //输错多少次后，账号锁定
                if (securityUserEntity.ErrorLoginCount > SecurityUserPolicy.ERROR_LOGIN_COUNT)
                {
                    securityUserEntity.IsAccountsLockedIndc = YesNoPolicy.YES;
                    securityUserEntity.AccountsLockedDatetime = DateTime.Now.AddDays(1);
                }

                repository.UpdateNow(securityUserEntity);

                throw new ValidatedException(certification);
            }

            JwtGenerate jwtGenerate = service.ServiceProvider.GetService<JwtGenerate>()!;

            string clainmName = Guid.NewGuid().ToString();
            Dictionary<string, string> claims = new Dictionary<string, string>();
            claims.Add(ClaimPolicy.CLAINM_NAME, clainmName);
            claims.Add(ClaimPolicy.CLAINM_ACCOUNT, securityUserEntity.UserMobile!);
            claims.Add(ClaimPolicy.CLAINM_USERID, securityUserEntity.UserName);
            claims.Add(ClaimPolicy.CLAINM_LOCALE, data.Culture);

            var accessToken = jwtGenerate.Generate(claims);
            if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization"))
                httpContextAccessor.HttpContext.Request.Headers.Remove("Authorization");
            httpContextAccessor.HttpContext.Request.Headers.Add("Authorization", "Bearer " + accessToken);

            if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey(ClaimPolicy.HEADER_LOCALE))
                httpContextAccessor.HttpContext.Request.Headers.Remove(ClaimPolicy.HEADER_LOCALE);
            httpContextAccessor.HttpContext.Request.Headers.Add(ClaimPolicy.HEADER_LOCALE, data.Culture);

            service.UserId = securityUserEntity.UserName;

            securityUserEntity.ErrorLoginCount = 0;
            securityUserEntity.IsAccountsLockedIndc = YesNoPolicy.NO;
            securityUserEntity.AccountsLockedDatetime = null;
            //更新最后登录日期
            securityUserEntity.LastLoginDatetime = DateTime.Now;

            repository.UpdateNow(securityUserEntity);

            IRepository<SecurityUserWebLoginEntity> repositoryLog = service.GetRepository<IRepository<SecurityUserWebLoginEntity>>();

            SecurityUserWebLoginEntity webLogin = new SecurityUserWebLoginEntity
            {
                Username = securityUserEntity.UserName,
                SessionId = clainmName,
                LoginTime = DateTime.Now,
                HostIp = httpContextAccessor.HttpContext.Connection.RemoteIpAddress?.MapToIPv4()?.ToString(),
                HostUserAgent = this.httpContextAccessor?.HttpContext?.Request.Headers.UserAgent.ToString(),
                RefreshToken = jwtGenerate.GenerateRefreshToken(),
                RefreshTokenRefreshedTime = DateTime.Now,
            };

            repositoryLog.InsertNow(webLogin);
            PermissionContext permissionContext = new PermissionContext(service);
            return new LoginProfile
            {
                Token = new JwtInfo() { AccessToken = accessToken, RefreshToken = webLogin.RefreshToken, CreatedTime = webLogin.RefreshTokenRefreshedTime },
                Username = securityUserEntity.UserName,
                UserType = securityUserEntity.UserType,
                UserLocalName = securityUserEntity.UserLocalName,
                UserEngName = securityUserEntity.UserEngName,
                UserMobile = securityUserEntity.UserMobile!,
                PermissionInfo = permissionContext.GetUserPermissionInfo()
            };
        }

        [PermissionOperate(name: SecurityPermissionPolicy.SecurityUser, operate: "LoginAgent")]
        [UnitOfWork]
        public LoginProfile LoginAgent(LoginData data)
        {
            LoginProfile loginProfile = Login(data);

            SecurityUserEntity agent = repository.DetachedEntities.Where(x =>
      (x.UserMobile == data.AgentUserName && x.UserIsValidIndc == YesNoPolicy.YES)).FirstOrDefault()!;

            if (agent == null)
            {
                throw new ValidatedException("代理用户不存在.");
            }

            JwtGenerate jwtGenerate = service.ServiceProvider.GetService<JwtGenerate>()!;

            Dictionary<string, string> claims = new Dictionary<string, string>();
            claims.Add(ClaimPolicy.CLAINM_NAME, agent.UserName);
            claims.Add(ClaimPolicy.CLAINM_ACCOUNT, agent.UserMobile!);
            claims.Add(ClaimPolicy.CLAINM_USERID, agent.UserName);
            claims.Add(ClaimPolicy.CLAINM_LOCALE, data.Culture);

            var accessToken = jwtGenerate.Generate(claims);
            if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization"))
                httpContextAccessor.HttpContext.Request.Headers.Remove("Authorization");
            httpContextAccessor.HttpContext.Request.Headers.Add("Authorization", "Bearer " + accessToken);

            if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey(ClaimPolicy.HEADER_LOCALE))
                httpContextAccessor.HttpContext.Request.Headers.Remove(ClaimPolicy.HEADER_LOCALE);
            httpContextAccessor.HttpContext.Request.Headers.Add(ClaimPolicy.HEADER_LOCALE, data.Culture);

            SecurityUserWebLoginEntity webLogin = new SecurityUserWebLoginEntity
            {
                Username = agent.UserName,
                SessionId = Guid.NewGuid().ToString(),
                LoginTime = DateTime.Now,
                HostIp = loginProfile.Username,
                HostUserAgent = loginProfile.UserMobile!,
                RefreshToken = jwtGenerate.GenerateRefreshToken(),
                RefreshTokenRefreshedTime = DateTime.Now,
            };

            IRepository<SecurityUserWebLoginEntity> repositoryLog = service.GetRepository<IRepository<SecurityUserWebLoginEntity>>();

            repositoryLog.InsertNow(webLogin);

            service.UserId = agent.UserName;
            PermissionContext permissionContext = new PermissionContext(service);
            return new LoginProfile
            {
                Token = new JwtInfo() { AccessToken = accessToken, RefreshToken = webLogin.RefreshToken, CreatedTime = webLogin.RefreshTokenRefreshedTime },
                Username = agent.UserName,
                UserType = agent.UserType,
                UserLocalName = agent.UserLocalName,
                UserEngName = agent.UserEngName,
                UserMobile = agent.UserMobile!,
                PermissionInfo = permissionContext.GetUserPermissionInfo()
            };
        }

        [AllowAnonymous]
        [UnitOfWork]
        public string Register(RegisterData data)
        {
            SecurityUserEntity securityUser = repository.DetachedEntities.Where(x => x.UserMobile == data.UserMobile).FirstOrDefault()!;
            if (securityUser != null)
            {
                throw new ValidatedException("手机号已注册.");
            }

            securityUser = new SecurityUserEntity();

            if (string.IsNullOrEmpty(data.UserMobile) || string.IsNullOrEmpty(data.RsaUserPassword)) return null;

            ISecurityGeneratorNumberService securityGeneratorNumberService = service.ServiceProvider.GetService<ISecurityGeneratorNumberService>()!;
            string userName = securityGeneratorNumberService.GetNo(SecurityPrefixPolicy.SECURITY_USER, SecurityPrefixPolicy.SECURITY_USER_USER_NAME, null, YesNoPolicy.NO, SecurityPrefixPolicy.SECURITY_USER_FORMAT);

            var privateKey = service.Configuration[SecuritySettingsPolicy.SecuritySettings_RsaPrivateKey];
            var rsa = new System.RSA(privateKey, true);

            securityUser.UserPassword = rsa.DecodeOrNull(data.RsaUserPassword);
            //052636a8-b146-9efd-f5dc-0034a3c5bb34+password(MD5)
            string guid = securityUser.UserPassword.Substring(0, 36);

            securityUser.UserPassword = securityUser.UserPassword.Substring(36, securityUser.UserPassword.Length - 36);
            securityUser.UserName = userName;
            securityUser.UserMobile = data.UserMobile;
            securityUser.UserLocalName = "用户" + securityUser.UserName;
            securityUser.UserIsValidIndc = YesNoPolicy.YES;

            JwtGenerate jwtGenerate = service.ServiceProvider.GetService<JwtGenerate>()!;

            Dictionary<string, string> claims = new Dictionary<string, string>();
            claims.Add(ClaimPolicy.CLAINM_NAME, securityUser.UserName);
            claims.Add(ClaimPolicy.CLAINM_ACCOUNT, securityUser.UserName);
            claims.Add(ClaimPolicy.CLAINM_USERID, securityUser.UserName);
            claims.Add(ClaimPolicy.CLAINM_LOCALE, CulturePolicy.ZH_CN);

            service.UserId = securityUser.UserName;

            var accessToken = jwtGenerate.Generate(claims);
            if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization"))
                httpContextAccessor.HttpContext.Request.Headers.Remove("Authorization");
            httpContextAccessor.HttpContext.Request.Headers.Add("Authorization", "Bearer " + accessToken);

            repository.InsertNow(securityUser);

            //发布注册的事务
            EventData eventData = new EventData();
            ISecurityUserRegisterPublisher publisher = service.ServiceProvider.GetService<ISecurityUserRegisterPublisher>()!;
            eventData.EventSource = securityUser;
            eventData.EventId = typeof(SecurityUserRegisterPublisher).Name;
            publisher.Publish(eventData);

            return securityUser.UserMobile;
        }

        [PermissionOperate(name: SecurityPermissionPolicy.SecurityUser, operate: Operate.Update)]
        [UnitOfWork]
        public LoginProfile AmendPassword(LoginData data)
        {
            SecurityUserEntity securityUserEntity = repository.DetachedEntities.Where(x =>
                     x.UserName == service.UserId
                     && x.UserIsValidIndc == YesNoPolicy.YES).FirstOrDefault()!;

            if (securityUserEntity == null)
            {
                throw new ValidatedException("认证不通过.");
            }

            var descKey = service.Configuration[SecuritySettingsPolicy.SecuritySettings_DescKey];

            var privateKey = service.Configuration[SecuritySettingsPolicy.SecuritySettings_RsaPrivateKey];
            var rsa = new RSA(privateKey, true);

            var psd = rsa.DecodeOrNull(data.RsaUserPassword);
            if (string.IsNullOrEmpty(psd)) throw new ValidatedException("认证不通过.");
            //052636a8-b146-9efd-f5dc-0034a3c5bb34+password
            var guid = psd.Substring(0, 36);


            if (guid != data.UserName)
            {
                throw new ValidatedException("认证不通过.");
            }

            if (securityUserEntity.IsAccountsLockedIndc == YesNoPolicy.YES && securityUserEntity.AccountsLockedDatetime.HasValue
                && securityUserEntity.AccountsLockedDatetime.Value.Subtract(DateTime.Now).TotalMinutes > 0)
            {
                throw new ValidatedException("用户账号已锁定.");
            }

            if (securityUserEntity.UserPassword != psd.Substring(36, psd.Length - 36))
            {
                securityUserEntity.ErrorLoginCount += 1;
                //输错多少次后，账号锁定
                if (securityUserEntity.ErrorLoginCount > SecurityUserPolicy.ERROR_LOGIN_COUNT)
                {
                    securityUserEntity.IsAccountsLockedIndc = YesNoPolicy.YES;
                    securityUserEntity.AccountsLockedDatetime = DateTime.Now.AddDays(1);
                }

                repository.UpdateNow(securityUserEntity);

                throw new ValidatedException("认证不通过.");
            }

            JwtGenerate jwtGenerate = service.ServiceProvider.GetService<JwtGenerate>()!;

            string clainmName = Guid.NewGuid().ToString();
            Dictionary<string, string> claims = new Dictionary<string, string>();
            claims.Add(ClaimPolicy.CLAINM_NAME, clainmName);
            claims.Add(ClaimPolicy.CLAINM_ACCOUNT, securityUserEntity.UserMobile!);
            claims.Add(ClaimPolicy.CLAINM_USERID, securityUserEntity.UserName);
            claims.Add(ClaimPolicy.CLAINM_LOCALE, data.Culture ?? CulturePolicy.ZH_CN);

            var accessToken = jwtGenerate.Generate(claims);
            if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization"))
                httpContextAccessor.HttpContext.Request.Headers.Remove("Authorization");
            httpContextAccessor.HttpContext.Request.Headers.Add("Authorization", "Bearer " + accessToken);

            securityUserEntity.ErrorLoginCount = 0;
            securityUserEntity.IsAccountsLockedIndc = YesNoPolicy.NO;
            securityUserEntity.AccountsLockedDatetime = null;
            //更新最后登录日期
            securityUserEntity.LastLoginDatetime = DateTime.Now;
            psd = rsa.DecodeOrNull(data.UserPassword);
            if (string.IsNullOrEmpty(psd)) throw new ValidatedException("认证不通过.");
            securityUserEntity.UserPassword = psd.Substring(36, psd.Length - 36);
            repository.UpdateNow(securityUserEntity);

            IRepository<SecurityUserWebLoginEntity> repositoryLog = service.GetRepository<IRepository<SecurityUserWebLoginEntity>>();

            SecurityUserWebLoginEntity webLogin = new SecurityUserWebLoginEntity
            {
                Username = securityUserEntity.UserName,
                SessionId = clainmName,
                LoginTime = DateTime.Now,
                HostIp = httpContextAccessor.HttpContext.Connection.RemoteIpAddress?.MapToIPv4()?.ToString(),
                HostUserAgent = this.httpContextAccessor?.HttpContext?.Request.Headers.UserAgent.ToString(),
                RefreshToken = jwtGenerate.GenerateRefreshToken(),
                RefreshTokenRefreshedTime = DateTime.Now,
            };

            repositoryLog.InsertNow(webLogin);
            PermissionContext permissionContext = new PermissionContext(service);
            return new LoginProfile
            {
                Token = new JwtInfo() { AccessToken = accessToken, RefreshToken = webLogin.RefreshToken, CreatedTime = webLogin.RefreshTokenRefreshedTime },
                Username = securityUserEntity.UserName,
                UserType = securityUserEntity.UserType,
                UserLocalName = securityUserEntity.UserLocalName,
                UserEngName = securityUserEntity.UserEngName,
                UserMobile = securityUserEntity.UserMobile!,
                PermissionInfo = permissionContext.GetUserPermissionInfo()
            };
        }


        [AllowAnonymous]
        [HttpGet]
        public string GetRsaPublicKey()
        {
            if (string.IsNullOrEmpty(service.Configuration["SecuritySettings:RsaPublicKey"]))
                throw new ValidatedException("[SecuritySettings:RsaPublicKey]");
            return service.Configuration["SecuritySettings:RsaPublicKey"]!;
        }

        [AllowAnonymous]
        public JwtInfo RefreshToken(JwtInfo jwtInfo)
        {
            if (jwtInfo == null || string.IsNullOrEmpty(jwtInfo.AccessToken) || string.IsNullOrEmpty(jwtInfo.RefreshToken))
                throw new ServiceErrorException("无效的令牌,请重新登录.");

            TokenValidationParameters parameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                //获取或设置要使用的Microsoft.IdentityModel.Tokens.SecurityKey用于签名验证。
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(service.CloudBuilder.CloudBuilderOptions.Jwt.Secret)),
                //获取或设置一个System.String，它表示将使用的有效发行者检查代币的发行者。
                ValidIssuer = service.CloudBuilder.CloudBuilderOptions.Jwt.Issuer,
                //获取或设置一个字符串，该字符串表示将用于检查的有效受众反对令牌的观众。
                ValidAudience = service.CloudBuilder.CloudBuilderOptions.Jwt.Audience,
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = false,//不验证过期时间
            };

            // 验证 Token
            var tokenHandler = new JwtSecurityTokenHandler();
            SecurityToken vTocken;
            string token = jwtInfo.AccessToken.Replace("Bearer ", "");
            try
            {
                var tokenValidationResult = tokenHandler.ValidateToken(token, parameters, out vTocken);

                if (tokenValidationResult == null || !tokenValidationResult.Identities.Any() || !tokenValidationResult.Identities.First().IsAuthenticated)
                    throw new ServiceErrorException("无效的令牌,请重新登录.");
            }
            catch (Exception)
            {
                throw new ServiceErrorException("无效的令牌,请重新登录.");
            }

            string clainmName = ClaimPolicy.GetClaimValue(token, ClaimPolicy.CLAINM_NAME);
            string username = ClaimPolicy.GetClaimValue(token, ClaimPolicy.CLAINM_USERID);
            string userMobile = ClaimPolicy.GetClaimValue(token, ClaimPolicy.CLAINM_ACCOUNT);
            string locale = ClaimPolicy.GetClaimValue(token, ClaimPolicy.CLAINM_LOCALE);
            if (string.IsNullOrEmpty(clainmName) || string.IsNullOrEmpty(username))
                throw new ServiceErrorException("无效的令牌,请重新登录.");

            IRepository<SecurityUserEntity> repositorySecurityUser = service.GetRepository<IRepository<SecurityUserEntity>>();

            SecurityUserEntity user = repositorySecurityUser.DetachedEntities.Where(x => x.UserName == username && x.UserIsValidIndc == YesNoPolicy.YES).FirstOrDefault();
            if (user == null) throw new ServiceErrorException("无效的令牌,请重新登录.");

            IRepository<SecurityUserWebLoginEntity> repositoryLog = service.GetRepository<IRepository<SecurityUserWebLoginEntity>>();

            SecurityUserWebLoginEntity log = repositoryLog.DetachedEntities.Where(x => x.Username == username && x.SessionId == clainmName).FirstOrDefault()!;

            //不是最后登录的令牌失效
            DateTime? LoginTime = repositoryLog.DetachedEntities.Where(x => x.Username == username).Max(x => x.LoginTime);
            if (LoginTime.HasValue && log.LoginTime < LoginTime.Value) throw new ServiceErrorException("无效的令牌,请重新登录.");

            if (log == null || log.RefreshToken != jwtInfo.RefreshToken || log.LogoutTime.HasValue ||
                log.RefreshToken != jwtInfo.RefreshToken || log.RefreshTokenRefreshedTime.AddMinutes(Convert.ToInt32(service.CloudBuilder.CloudBuilderOptions.Jwt.RefreshExpireMins)) < DateTime.Now)
                throw new ServiceErrorException("无效的令牌,请重新登录.");

            JwtGenerate jwtGenerate = service.ServiceProvider.GetService<JwtGenerate>()!;

            Dictionary<string, string> claims = new Dictionary<string, string>();
            claims.Add(ClaimPolicy.CLAINM_NAME, clainmName);
            claims.Add(ClaimPolicy.CLAINM_ACCOUNT, userMobile);
            claims.Add(ClaimPolicy.CLAINM_USERID, username);
            claims.Add(ClaimPolicy.CLAINM_LOCALE, locale??CulturePolicy.ZH_CN);

            var accessToken = jwtGenerate.Generate(claims);

            log.RefreshToken = jwtGenerate.GenerateRefreshToken();
            log.RefreshTokenRefreshedTime = DateTime.Now;

            if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization"))
                httpContextAccessor.HttpContext.Request.Headers.Remove("Authorization");
            httpContextAccessor.HttpContext.Request.Headers.Add("Authorization", "Bearer " + accessToken);

            repositoryLog.UpdateNow(log);

            jwtInfo.AccessToken = accessToken;
            jwtInfo.RefreshToken = log.RefreshToken;
            jwtInfo.CreatedTime = log.RefreshTokenRefreshedTime;

            return jwtInfo;
        }

        [AllowAnonymous]
        public void Logout()
        {
            string username = ClaimPolicy.GetClaimValue(httpContextAccessor, ClaimPolicy.CLAINM_USERID);
            string clainmName = ClaimPolicy.GetClaimValue(httpContextAccessor, ClaimPolicy.CLAINM_NAME);

            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(clainmName)) { return; }


            IRepository<SecurityUserWebLoginEntity> repositoryLog = service.GetRepository<IRepository<SecurityUserWebLoginEntity>>();

            SecurityUserWebLoginEntity log = repositoryLog.DetachedEntities.Where(x => x.Username == username && x.SessionId == clainmName).FirstOrDefault()!;
            if (log == null || log.LogoutTime.HasValue) { return; }

            log.LogoutTime = DateTime.Now;

            repositoryLog.UpdateNow(log);
        }

    }
}
