#region copyright
//------------------------------------------------------------------------------
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
//  源代码使用协议遵循本仓库的开源协议及附加协议
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
//  QQ群:605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DataEncryption;
using Furion.EventBus;
using Furion.FriendlyException;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Security.Claims;
using ThingsGateway.Foundation.Extension.String;
using Yitter.IdGenerator;
namespace ThingsGateway.Admin.Application;
/// 
public class AuthService : IAuthService
{
    private readonly IConfigService _configService;
    private readonly IEventPublisher _eventPublisher;
    private readonly INoticeService _noticeService;
    private readonly IServiceScope _serviceScope;
    private readonly ISysUserService _userService;
    private readonly IVerificatService _verificatService;
    /// 
    public AuthService(
                       IEventPublisher eventPublisher,
                       ISysUserService userService,
                       IConfigService configService,
                       IVerificatService verificatService,
                        INoticeService noticeService, IServiceScopeFactory serviceScopeFactory
        )
    {
        _eventPublisher = eventPublisher;
        _userService = userService;
        _configService = configService;
        _verificatService = verificatService;
        _noticeService = noticeService;
        _serviceScope = serviceScopeFactory.CreateScope();
    }
    /// 
    public ValidCodeOutput GetCaptchaInfo()
    {
        //生成验证码
        var captchInfo = new Random().Next(1111, 9999).ToString();
        //生成请求号,并将验证码放入cache
        var reqNo = YitIdHelper.NextId();
        //插入cache
        _serviceScope.ServiceProvider.GetService().Set(CacheConst.LOGIN_CAPTCHA + reqNo, captchInfo, TimeSpan.FromMinutes(1), false);
        //返回验证码和请求号
        return new ValidCodeOutput { CodeValue = captchInfo, ValidCodeReqNo = reqNo };
    }
    /// 
    public async Task GetLoginUserAsync()
    {
        return await _userService.GetUserByIdAsync(UserManager.UserId);
    }
    /// 
    public async Task LoginAsync(LoginInput input)
    {
        //判断是否有验证码
        var sysBase = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_CAPTCHA_OPEN);
        if (sysBase != null)//如果有这个配置项
        {
            if (sysBase.ConfigValue.ToBool(false))//如果需要验证码
            {
                //如果没填验证码,提示验证码不能为空
                if (input.ValidCode.IsNullOrEmpty() || input.ValidCodeReqNo == 0) throw Oops.Bah("验证码不能为空").StatusCode(410);
                ValidValidCode(input.ValidCode, input.ValidCodeReqNo);//校验验证码
            }
        }
        var password = DESCEncryption.Decrypt(input.Password, DESCKeyConst.DESCKey);  // 解密
        var userInfo = await _userService.GetUserByAccountAsync(input.Account) ?? throw Oops.Bah("用户不存在");//获取用户信息
        if (userInfo.Password != password) throw Oops.Bah("账号密码错误");//账号密码错误
        return await LoginAsync(userInfo, input.Device);
    }
    /// 
    public async Task LogoutAsync()
    {
        //获取用户信息
        var userinfo = await _userService.GetUserByAccountAsync(UserManager.UserAccount);
        if (userinfo != null)
        {
            LoginEvent loginEvent = new()
            {
                Ip = App.HttpContext.GetRemoteIpAddressToIPv4(),
                SysUser = userinfo,
                VerificatId = UserManager.VerificatId.ToLong(),
            };
            await RemoveVerificatAsync(loginEvent);//移除验证Id
        }
    }
    /// 
    /// 校验验证码方法
    /// 
    /// 验证码
    /// 请求号
    /// 是否从Cache删除
    private void ValidValidCode(string validCode, long validCodeReqNo, bool isDelete = true)
    {
        var code = _serviceScope.ServiceProvider.GetService().Get(CacheConst.LOGIN_CAPTCHA + validCodeReqNo, false);//从cache拿数据
        if (isDelete) _serviceScope.ServiceProvider.GetService().Remove(CacheConst.LOGIN_CAPTCHA + validCodeReqNo);//删除验证码
        if (code != null)//如果有
        {
            //验证码如果不匹配直接抛错误,这里忽略大小写
            if (validCode.ToLower() != code.ToLower()) throw Oops.Bah("验证码错误");
        }
        else
        {
            throw Oops.Bah("验证码已过期");
        }
    }
    /// 
    /// 执行B端登录
    /// 
    /// 用户信息
    /// 登录设备
    /// 
    private async Task LoginAsync(SysUser sysUser, AuthDeviceTypeEnum device)
    {
        if (sysUser.UserEnable == false) throw Oops.Bah("账号已停用");//账号已停用
        var sysBase = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_VERIFICAT_EXPIRES);
        var sessionid = YitIdHelper.NextId();
        var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
        identity.AddClaim(new Claim(ClaimConst.VerificatId, sessionid.ToString()));
        identity.AddClaim(new Claim(ClaimConst.UserId, sysUser.Id.ToString()));
        identity.AddClaim(new Claim(ClaimConst.Account, sysUser.Account));
        identity.AddClaim(new Claim(ClaimConst.IsSuperAdmin, sysUser.RoleCodeList.Contains(RoleConst.SuperAdmin).ToString()));
        identity.AddClaim(new Claim(ClaimConst.IsOpenApi, false.ToString()));
        var config = sysBase.ConfigValue.ToInt(2880);
        var diffTime = DateTimeExtensions.CurrentDateTime.AddMinutes(config);
        await App.HttpContext.SignInAsync(new ClaimsPrincipal(identity), new AuthenticationProperties()
        {
            IsPersistent = true,
            ExpiresUtc = diffTime,
        });
        //登录事件参数
        var loginEvent = new LoginEvent
        {
            Ip = App.HttpContext.GetRemoteIpAddressToIPv4(),
            Device = device,
            Expire = config,
            SysUser = sysUser,
            VerificatId = sessionid,
        };
        await SetVerificatAsync(loginEvent);//写入verificat
        await _eventPublisher.PublishAsync(EventSubscriberConst.Login, loginEvent); //发布登录事件总线
        return new LoginOutput { VerificatId = sessionid, Account = sysUser.Account };
    }
    private async Task RemoveVerificatAsync(LoginEvent loginEvent)
    {
        //获取verificat列表
        List verificatInfos = await _verificatService.GetVerificatIdAsync(loginEvent.SysUser.Id);
        if (verificatInfos != null)
        {
            //获取当前用户的verificat
            var verificat = verificatInfos.Where(it => it.Id == loginEvent.VerificatId).FirstOrDefault();
            if (verificat != null)
                verificatInfos.Remove(verificat);
            //更新verificat列表
            await _verificatService.SetVerificatIdAsync(loginEvent.SysUser.Id, verificatInfos);
        }
        await App.HttpContext?.SignOutAsync();
        App.HttpContext?.SignoutToSwagger();
    }
    /// 
    /// 写入验证信息到缓存
    /// 
    /// 
    /// 
    private async Task SetVerificatAsync(LoginEvent loginEvent)
    {
        //获取verificat列表
        List verificatInfos = await _verificatService.GetVerificatIdAsync(loginEvent.SysUser.Id);
        var verificatTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire);
        //生成verificat信息
        var verificatInfo = new VerificatInfo
        {
            Device = loginEvent.Device.ToString(),
            Expire = loginEvent.Expire,
            VerificatTimeout = verificatTimeout,
            Id = loginEvent.VerificatId,
            UserId = loginEvent.SysUser.Id,
        };
        if (verificatInfos != null)
        {
            bool isSingle = false;//默认不开启单用户登录
            var singleConfig = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SINGLE_OPEN);//获取系统单用户登录选项
            if (singleConfig != null) isSingle = singleConfig.ConfigValue.ToBool(false);//如果配置不为空则设置单用户登录选项为系统配置的值
            if (isSingle)//判断是否单用户登录
            {
                await _noticeService.LogoutAsync(loginEvent.SysUser.Id, verificatInfos.Where(it => it.Device == loginEvent.Device.ToString()).ToList(), "该账号已在别处登录!");//通知其他用户下线
                verificatInfos = verificatInfos.Where(it => it.Device != loginEvent.Device.ToString()).ToList();//去掉当前登录类型
                verificatInfos.Add(verificatInfo);//添加到列表
            }
            else
            {
                verificatInfos.Add(verificatInfo);
            }
        }
        else
        {
            verificatInfos = new List { verificatInfo };//直接就一个
        }
        //添加到verificat列表
        await _verificatService.SetVerificatIdAsync(loginEvent.SysUser.Id, verificatInfos);
    }
}