using System.Security.Claims;
using Fw.Utility.UnifyResponse;
using Hotline.Caching.Interfaces;
using Hotline.Caching.Services;
using Hotline.Identity;
using Hotline.Identity.Accounts;
using Hotline.Orders;
using Hotline.Push;
using Hotline.Schedulings;
using Hotline.SeedData;
using Hotline.Settings;
using Hotline.Share.Dtos.FlowEngine;
using Hotline.Share.Dtos.Identity;
using Hotline.Share.Dtos.Snapshot;
using Hotline.Share.Enums.FlowEngine;
using Hotline.Share.Enums.Identity;
using Hotline.Share.Enums.Snapshot;
using Hotline.Share.Enums.User;
using Hotline.Share.Tools;
using Hotline.Snapshot.IRepository;
using Hotline.ThirdAccountDomainServices;
using Hotline.ThirdAccountDomainServices.Interfaces;
using Hotline.Users;
using IdentityModel;
using Mapster;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using SqlSugar;
using XF.Domain.Authentications;
using XF.Domain.Cache;
using XF.Domain.Dependency;
using XF.Domain.Exceptions;
using XF.Domain.Options;
using XF.Domain.Repository;

namespace Hotline.Application.Identity;

public class IdentityAppService : IIdentityAppService, IScopeDependency
{
    private readonly IAccountRepository _accountRepository;
    private readonly IAccountDomainService _accountDomainService;
    private readonly IIdentityDomainService _identityDomainService;
    private readonly IRepository<User> _userRepository;
    private readonly IJwtSecurity _jwtSecurity;
    private readonly IOptionsSnapshot<IdentityConfiguration> _identityOptionsAccessor;
    private readonly ITypedCache<AudienceTicket> _cacheAudience;
    private readonly IMessageCodeDomainService _messageCodeDomainService;
    private readonly IRepository<Scheduling> _schedulingRepository;
    private readonly IOrderDomainService _orderDomainService;
    private readonly ISystemSettingCacheManager _systemSettingCacheManager;
    private readonly IThirdAccountRepository _thirdAccountRepository;
    private readonly ISystemLogRepository _systemLog;
    private readonly ThirdIdentiyFactory _thirdIdentiyFactory;
    private readonly ThirdAccounSupplierFactory _thirdAccountDomainFactory;

    public IdentityAppService(
        IAccountRepository accountRepository,
        IAccountDomainService accountDomainService,
        IIdentityDomainService identityDomainService,
        IRepository<User> userRepository,
        IJwtSecurity jwtSecurity,
        IOptionsSnapshot<IdentityConfiguration> identityOptionsAccessor,
        ITypedCache<AudienceTicket> cacheAudience,
        IMessageCodeDomainService messageCodeDomainService,
        IRepository<Scheduling> schedulingRepository,
        IOrderDomainService orderDomainService,
        ISystemSettingCacheManager systemSettingCacheManager,
        IThirdAccountRepository thirdAccountRepository,
        ISystemLogRepository systemLog,
        ThirdIdentiyFactory thirdIdentiyFactory,
        ThirdAccounSupplierFactory thirdAccountDomainFactory)
    {
        _accountRepository = accountRepository;
        _accountDomainService = accountDomainService;
        _identityDomainService = identityDomainService;
        _userRepository = userRepository;
        _jwtSecurity = jwtSecurity;
        _identityOptionsAccessor = identityOptionsAccessor;
        _cacheAudience = cacheAudience;
        _messageCodeDomainService = messageCodeDomainService;
        _schedulingRepository = schedulingRepository;
        _orderDomainService = orderDomainService;
        _systemSettingCacheManager = systemSettingCacheManager;
        _thirdAccountRepository = thirdAccountRepository;
        _systemLog = systemLog;
        _thirdIdentiyFactory = thirdIdentiyFactory;
        _thirdAccountDomainFactory = thirdAccountDomainFactory;
    }

    public async Task<string> OldToNewLoginAsync(HotlineLoginOldToNewDto dto, CancellationToken cancellationToken)
    {
        var account = await _accountRepository.GetExtAsync(
           d => d.UserName == dto.UserName,
           d => d.Includes(x => x.Roles));

        if (account is null)
            throw UserFriendlyException.SameMessage("未找到帐号，请联系管理员查看");

        if (account.Status != EAccountStatus.Normal)
            throw UserFriendlyException.SameMessage("帐号已被注销，请联系管理员查看");

        if (account.LockoutEnabled && account.LockoutEnd >= DateTime.Now)
            throw UserFriendlyException.SameMessage("账号被锁定！请联系管理员查看");

        var userInfo = await _userRepository.GetAsync(p => p.Id == account.Id, cancellationToken);

        //限制系统类型账户频繁获取token的行为
        //todo

        if (account.LockoutEnd.HasValue || account.AccessFailedCount > 0)
        {
            account.LockoutEnd = null;
            account.AccessFailedCount = 0;
            await _accountRepository.UpdateAsync(account, cancellationToken);
        }

        var user = await _userRepository.Queryable()
            .Includes(d => d.Organization)
            .FirstAsync(d => d.Id == account.Id);

        //平均派单
        var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
        if (averageSendOrder)
        {
            await AverageOrderScheduling(account.Id, cancellationToken);
        }
        var jwtOptions = _identityOptionsAccessor.Value.Jwt;
        var claims = new List<Claim>
        {
            //new(JwtClaimTypes.Id, account.Id),
            new(JwtClaimTypes.Subject, account.Id),
            new(JwtClaimTypes.PhoneNumber, account.PhoneNo ?? string.Empty),
            new(AppClaimTypes.UserDisplayName, account.Name),
            //new(JwtClaimTypes.Scope, jwtOptions.Scope),
            new(JwtClaimTypes.Scope, account.Scope),
            new(AppClaimTypes.UserPasswordChanged, account.PasswordChanged.ToString()),
            new(AppClaimTypes.StaffNo, user.StaffNo ?? string.Empty),
        };
        if (!string.IsNullOrEmpty(user.OrgId) && user.Organization is not null)
        {
            claims.AddRange(
                new List<Claim>
                {
                    new(AppClaimTypes.DepartmentId, user.OrgId ?? string.Empty),
                    new(AppClaimTypes.DepartmentIsCenter, user.Organization?.IsCenter.ToString()),
                    new(AppClaimTypes.DepartmentName, user.Organization?.Name ?? string.Empty),
                    new(AppClaimTypes.DepartmentAreaCode, user.Organization?.AreaCode ?? string.Empty),
                    new(AppClaimTypes.DepartmentAreaName, user.Organization?.AreaName ?? string.Empty),
                    new(AppClaimTypes.DepartmentLevel, user.Organization?.Level.ToString() ?? string.Empty),
                    new(AppClaimTypes.AreaId, user.OrgId?.GetHigherOrgId() ?? string.Empty),
                }
            );
        }
        claims.AddRange(account.Roles.Select(d => new Claim(JwtClaimTypes.Role, d.Name)));
        var audience = new AudienceTicket(account.Id);
        var expiredSeconds = jwtOptions.Expired <= 0 ? 3600 : jwtOptions.Expired;
        await _cacheAudience.SetAsync(audience.Id, audience, TimeSpan.FromSeconds(expiredSeconds), cancellationToken);
        var token = _jwtSecurity.EncodeJwtToken(claims, audience.Ticket);
        return token;
    }

    public async Task<(bool,bool, User)> IsCheckAdmin(string userName)
    {
        var isAdmin = false;
        var isCenter = false;
        var account = await _accountRepository.GetExtAsync(
           d => d.UserName == userName,
           d => d.Includes(x => x.Roles)
           );

        if (account is null)
            throw UserFriendlyException.SameMessage("未找到帐号，请联系管理员查看");

        if (account.Status != EAccountStatus.Normal)
            throw UserFriendlyException.SameMessage("帐号已被注销，请联系管理员查看");

        if (account.LockoutEnabled && account.LockoutEnd >= DateTime.Now)
            throw UserFriendlyException.SameMessage("账号被锁定！请联系管理员查看");

        var user = await _userRepository.Queryable()
            .Includes(d => d.Organization)
            .Includes(d => d.Roles)
            .FirstAsync(d => d.Id == account.Id);

        var systemAdministrator = _systemSettingCacheManager.GetSetting(SettingConstants.SystemAdministrator)?.SettingValue[0];
        if (!string.IsNullOrEmpty(systemAdministrator) && (user.Roles.Any(x=>x.Name.Contains(systemAdministrator)) || user.Roles.Any(x=>x.Name.Contains(RoleSeedData.AdminRole))))
            isAdmin = true;
        else
            isAdmin = false;

        isCenter = user.Organization.IsCenter;

        return (isAdmin, isCenter,user);
    }

    /// <summary>
    /// 登录
    /// </summary>
    /// <param name="request"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task<string> LoginWithSignatureAsync(LoginWithSignatureRequest request, CancellationToken cancellationToken)
    {
        if (!_identityDomainService.IsIdentityReal(request))
            throw UserFriendlyException.SameMessage("用户名或密码错误！");
        var token = await LoginAsync(request, cancellationToken);
        if(string.IsNullOrEmpty(token))
            throw UserFriendlyException.SameMessage("用户名或密码错误！");
        _identityDomainService.SetAccountNonce(request.Username, request.Nonce);
        return token;
    }

    public async Task<string> LoginAsync(LoginDto dto, CancellationToken cancellationToken)
    {
        var account = await _accountRepository.GetExtAsync(
            d => d.UserName == dto.Username,
            d => d.Includes(x => x.Roles));
        if (account == null)
            throw new UserFriendlyException($"用户名或密码错误！{System.Text.Json.JsonSerializer.Serialize(dto)}", "用户名或密码错误！");

        var userInfo = await _userRepository.GetAsync(p => p.Id == account.Id, cancellationToken);

        //校验验证码
        await _messageCodeDomainService.CheckdCode(account.UserName, userInfo.PhoneNo, dto.MsgCode, cancellationToken);

        if (account.Status != EAccountStatus.Normal)
            throw UserFriendlyException.SameMessage("用户名或密码错误！");

        if (account.LockoutEnabled && account.LockoutEnd >= DateTime.Now)
            throw UserFriendlyException.SameMessage("账号被锁定！");

        var verifyResult = _accountDomainService.VerifyPassword(account, dto.Password);
        if (verifyResult == PasswordVerificationResult.Failed)
        {
            var lockoutOptions = _identityOptionsAccessor.Value.Lockout;
            account.AccessFailedCount += 1;
            if (account.LockoutEnabled && account.AccessFailedCount >= lockoutOptions.MaxFailedAccessAttempts)
                account.LockoutEnd = DateTime.Now.Add(lockoutOptions.DefaultLockoutTimeSpan);
            await _accountRepository.UpdateAsync(account, cancellationToken);
            throw new UserFriendlyException($"用户名或密码错误！{System.Text.Json.JsonSerializer.Serialize(dto)}", "用户名或密码错误！");
        }

        //限制系统类型账户频繁获取token的行为
        //todo

        if (account.LockoutEnd.HasValue || account.AccessFailedCount > 0)
        {
            account.LockoutEnd = null;
            account.AccessFailedCount = 0;
            await _accountRepository.UpdateAsync(account, cancellationToken);
        }

        var user = await _userRepository.Queryable()
            .Includes(d => d.Organization)
            .FirstAsync(d => d.Id == account.Id);
        if (user == null)
            throw UserFriendlyException.SameMessage("未查询到用户数据");
        //平均派单
        var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
        if (averageSendOrder)
        {
            await AverageOrderScheduling(account.Id, cancellationToken);
        }
        var jwtOptions = _identityOptionsAccessor.Value.Jwt;
        var claims = new List<Claim>
        {
            //new(JwtClaimTypes.Id, account.Id),
            new(JwtClaimTypes.Subject, account.Id),
            new(JwtClaimTypes.PhoneNumber, account.PhoneNo ?? string.Empty),
            new(AppClaimTypes.UserDisplayName, account.Name),
            //new(JwtClaimTypes.Scope, jwtOptions.Scope),
            new(JwtClaimTypes.Scope, account.Scope),
            new(AppClaimTypes.UserPasswordChanged, account.PasswordChanged.ToString()),
            new(AppClaimTypes.StaffNo, user.StaffNo ?? string.Empty),
        };
        if (!string.IsNullOrEmpty(user.OrgId) && user.Organization is not null)
        {
            claims.AddRange(
                new List<Claim>
                {
                    new(AppClaimTypes.DepartmentId, user.OrgId ?? string.Empty),
                    new(AppClaimTypes.DepartmentIsCenter, user.Organization?.IsCenter.ToString()),
                    new(AppClaimTypes.DepartmentName, user.Organization?.Name ?? string.Empty),
                    new(AppClaimTypes.DepartmentAreaCode, user.Organization?.AreaCode ?? string.Empty),
                    new(AppClaimTypes.DepartmentAreaName, user.Organization?.AreaName ?? string.Empty),
                    new(AppClaimTypes.DepartmentLevel, user.Organization?.Level.ToString() ?? string.Empty),
                    new(AppClaimTypes.AreaId, user.OrgId?.GetHigherOrgId() ?? string.Empty),
                }
            );
        }
        claims.AddRange(account.Roles.Select(d => new Claim(JwtClaimTypes.Role, d.Name)));
        var audience = new AudienceTicket(account.Id);
        var expiredSeconds = jwtOptions.Expired <= 0 ? 3600 : jwtOptions.Expired;
        await _cacheAudience.SetAsync(audience.Id, audience, TimeSpan.FromSeconds(expiredSeconds), cancellationToken);
        var token = _jwtSecurity.EncodeJwtToken(claims, audience.Ticket);
        return token;
    }

    public async Task AverageOrderScheduling(string id, CancellationToken cancellationToken)
    {
        try
        {
            DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));

            //&& x.AtWork!.Value != true
            //根据当前时间获取排班信息
            var scheduling = await _schedulingRepository.Queryable()
                .Includes(x => x.SchedulingUser)
                .Where(x => x.SchedulingTime == time &&
                            //(x.AtWork == true || x.AtWork == null) &&
                            x.SchedulingUser.UserId == id)
                .OrderBy(x => x.SendOrderNum).FirstAsync(cancellationToken);
            if (scheduling != null && (!scheduling.AtWork.HasValue || scheduling.AtWork == false))
            {
                scheduling.AtWork = true;
                //执行登录平均派单
                await _orderDomainService.LogAverageOrder(id, scheduling, cancellationToken);
            }
        }
        catch
        {
            // ignored
        }
    }

    /// <summary>
    /// 获取三方令牌
    /// </summary>
    /// <param name="dto"></param>
    /// <returns></returns>
    /// <exception cref="UserFriendlyException"></exception>
    public async Task<Dictionary<string, object>> GetThirdTokenAsync(ThirdTokenInDto dto, CancellationToken token)
    {
        var thirdDto = dto.Adapt<ThirdTokenDto>();
        thirdDto = await _thirdAccountDomainFactory.GetThirdParameterAsync(thirdDto, token);
        var thirdToken = await _thirdIdentiyFactory.GetTokenAsync(thirdDto, token);
        var phone = await _thirdIdentiyFactory.GetPhoneNumberAsync(thirdDto, token);

        var inDto = new ThirdOpenIdInDto
        { 
            OpenId = thirdToken.OpenId,
            UnionId = thirdToken.UnIonId,
            AppType = dto.AppType,
            ThirdType = dto.ThirdType,
            PhoneNumber = phone.PhoneNumber
        };
        return await GetThirdTokenAsync(inDto, token);
    }

    public async Task<Dictionary<string, object>> GetThirdTokenAsync(ThirdOpenIdInDto dto, CancellationToken token)
    {
        var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(dto.OpenId, token);

        // 新用户注册
        if (thirdAccount is null)
        {
            thirdAccount = dto.Adapt<ThirdAccount>();
            thirdAccount.PhoneNumber = dto.PhoneNumber;
            thirdAccount.Id = await _thirdAccountRepository.AddAsync(thirdAccount);
            await _thirdAccountDomainFactory.RegisterAsync(thirdAccount, token);
            thirdAccount = await _thirdAccountRepository.GetAsync(thirdAccount.Id, token);
        }

        return await GetJwtToken(thirdAccount, token);
    }

    public async Task<Dictionary<string, object>> RefreshTokenAsync(string openId, CancellationToken token)
    {
        var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(openId, token);
        if (thirdAccount is null)
        {
            throw new UserFriendlyException(403, "用户不存在");
        }

        return await GetJwtToken(thirdAccount, token);
    }

    private async Task<Dictionary<string, object>> GetJwtToken(ThirdAccount thirdAccount, CancellationToken cancel)
    {
        var jwtOptions = _identityOptionsAccessor.Value.Jwt;
        var claims = new List<Claim>
        {
            new(JwtClaimTypes.PhoneNumber, thirdAccount.PhoneNumber ?? string.Empty),
            new(JwtClaimTypes.Scope, jwtOptions.Scope),//todo 三方账号的scope
            new(AppClaimTypes.OpenId, thirdAccount.OpenId),
        };
        var userId = await _thirdAccountDomainFactory.GetClaimAsync(thirdAccount, claims, cancel);
        var audience = new AudienceTicket(userId);
        var expiredSeconds = jwtOptions.Expired <= 0 ? 3600 : jwtOptions.Expired;
        await _cacheAudience.SetAsync(audience.Id, audience, TimeSpan.FromSeconds(expiredSeconds));
        var token = _jwtSecurity.EncodeJwtToken(claims, audience.Ticket);
        
        var dicOutData = new Dictionary<string, object>()
        {
            { "token", token },
            { "openId", thirdAccount.OpenId },
            { "phoneNumber", thirdAccount.PhoneNumber },
            { "userName", thirdAccount.UserName }
        };
        dicOutData = await _thirdAccountDomainFactory.GetLoginOutDataAsync(thirdAccount, dicOutData, cancel);
        return dicOutData;
    }
}