using Hotline.Share.Enums.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using XF.Domain.Dependency;
using XF.Domain.Exceptions;
using XF.Domain.Options;
using XF.Domain.Password;

namespace Hotline.Identity.Accounts;

public class AccountDomainService : IAccountDomainService, IScopeDependency
{
    private readonly IPasswordHasher<Account> _passwordHasher;
    private readonly IAppPasswordValidator _appPasswordValidator;
    private readonly IAccountRepository _accountRepository;
    private readonly IOptionsSnapshot<IdentityConfiguration> _identityOptionsAccessor;

    public AccountDomainService(
        IPasswordHasher<Account> passwordHasher,
        IAppPasswordValidator appPasswordValidator,
        IAccountRepository accountRepository,
        IOptionsSnapshot<IdentityConfiguration> identityOptionsAccessor)
    {
        _passwordHasher = passwordHasher;
        _appPasswordValidator = appPasswordValidator;
        _accountRepository = accountRepository;
        _identityOptionsAccessor = identityOptionsAccessor;
    }

    public async Task<IdentityResult> ResetPasswordAsync(Account account, string password, string newPassword, CancellationToken cancellationToken = default)
    {
        if (account == null)
            throw new ArgumentNullException(nameof(account));

        var verifyResult = VerifyPassword(account, password);
        if (verifyResult == PasswordVerificationResult.Failed)
            return IdentityResult.Failed(new IdentityError { Code = nameof(PasswordVerificationResult), Description = "密码校验失败" });
        var result = await UpdatePasswordHashAsync(account, newPassword, validatePassword: true, cancellationToken);
        return result;
    }

    public Task<IdentityResult> InitialPasswordAsync(Account account, CancellationToken cancellationToken = default)
    {
        var accountOptions = _identityOptionsAccessor.Value.Account;
        return UpdatePasswordHashAsync(account, accountOptions.DefaultPassword, true, cancellationToken);
    }

    public async Task<IdentityResult> UpdatePasswordHashAsync(Account account, string newPassword, bool validatePassword = true, CancellationToken cancellationToken = default)
    {
        if (account == null)
            throw new ArgumentNullException(nameof(account));

        if (validatePassword)
        {
            var validate = await _appPasswordValidator.ValidateAsync(newPassword);
            if (!validate.Succeeded)
                return validate;
        }
        var hash = newPassword != null ? _passwordHasher.HashPassword(account, newPassword) : null;
        account.PasswordHash = hash;
        await _accountRepository.UpdateAsync(account, cancellationToken);

        //await UpdateSecurityStampInternal(user);//todo
        return IdentityResult.Success;
    }

    public PasswordVerificationResult VerifyPassword(Account account, string pwd)
    {
        var hash = account.PasswordHash;
        if (string.IsNullOrEmpty(hash))
            return PasswordVerificationResult.Failed;
        return _passwordHasher.VerifyHashedPassword(account, hash, pwd);
    }

    public bool IsLockedOut(Account account)
    {
        if (account == null)
            throw new ArgumentNullException(nameof(account));
        if (!account.LockoutEnabled)
            return false;
        if (!account.LockoutEnd.HasValue)
            return false;

        return account.LockoutEnd >= DateTimeOffset.UtcNow;
    }

    public async Task LockOutAsync(Account account, DateTime? lockoutEnd = null, CancellationToken cancellationToken = default)
    {
        if (account == null)
            throw new ArgumentNullException(nameof(account));
        if (!account.LockoutEnabled)
            throw UserFriendlyException.SameMessage("该账号不允许锁定");

        account.LockoutEnd = lockoutEnd ?? DateTime.MaxValue;
        await _accountRepository.UpdateAsync(account, cancellationToken);
    }

    public async Task UnRegisterAsync(Account account, CancellationToken cancellationToken)
    {
        if (account == null)
            throw new ArgumentNullException(nameof(account));
        if (account.Status != EAccountStatus.UnRegister)
        {
            account.Status = EAccountStatus.UnRegister;
            await _accountRepository.UpdateAsync(account, cancellationToken);
        }
    }
}