namespace MyOffice.Web.Identity.Repositories;

using System.Security.Cryptography;
using Domain;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;

/// <summary>
/// Identity V3 hasher for new passwords; still verifies the legacy
/// (16-byte salt + 20-byte PBKDF2-SHA1 @ 100k) format and signals rehash.
/// </summary>
public class PasswordHasher : IPasswordHasher<ApplicationUser<Guid>>
{
	private const int LegacySaltSize = 16;
	private const int LegacyHashSize = 20;
	private const int LegacyIterations = 100_000;
	private const int LegacyPayloadSize = LegacySaltSize + LegacyHashSize;

	private readonly PasswordHasher<ApplicationUser<Guid>> _identityHasher = new();

	public string HashPassword(ApplicationUser<Guid> user, string password) =>
		_identityHasher.HashPassword(user, password);

	public PasswordVerificationResult VerifyHashedPassword(
		ApplicationUser<Guid> user,
		string hashedPassword,
		string providedPassword
	)
	{
		if (string.IsNullOrEmpty(hashedPassword) || providedPassword == null)
		{
			return PasswordVerificationResult.Failed;
		}

		// Prefer modern Identity format when payload is not the legacy 36-byte blob.
		if (!IsLegacyPayload(hashedPassword))
		{
			return _identityHasher.VerifyHashedPassword(user, hashedPassword, providedPassword);
		}

		if (VerifyLegacyHash(providedPassword, hashedPassword))
		{
			return PasswordVerificationResult.SuccessRehashNeeded;
		}

		return PasswordVerificationResult.Failed;
	}

	private static bool IsLegacyPayload(string hashedPassword)
	{
		try
		{
			var bytes = Convert.FromBase64String(hashedPassword);
			return bytes.Length == LegacyPayloadSize;
		}
		catch (FormatException)
		{
			return false;
		}
	}

	internal static string HashLegacyForTests(string password)
	{
		var salt = RandomNumberGenerator.GetBytes(LegacySaltSize);
		var hash = Rfc2898DeriveBytes.Pbkdf2(
			password,
			salt,
			LegacyIterations,
			HashAlgorithmName.SHA1,
			LegacyHashSize);

		var payload = new byte[LegacyPayloadSize];
		Buffer.BlockCopy(salt, 0, payload, 0, LegacySaltSize);
		Buffer.BlockCopy(hash, 0, payload, LegacySaltSize, LegacyHashSize);
		return Convert.ToBase64String(payload);
	}

	private static bool VerifyLegacyHash(string password, string passwordHash)
	{
		byte[] hashBytes;
		try
		{
			hashBytes = Convert.FromBase64String(passwordHash);
		}
		catch (FormatException)
		{
			return false;
		}

		if (hashBytes.Length != LegacyPayloadSize)
		{
			return false;
		}

		var salt = hashBytes.AsSpan(0, LegacySaltSize);
		var expected = hashBytes.AsSpan(LegacySaltSize, LegacyHashSize);
		var actual = Rfc2898DeriveBytes.Pbkdf2(
			password,
			salt,
			LegacyIterations,
			HashAlgorithmName.SHA1,
			LegacyHashSize);

		return CryptographicOperations.FixedTimeEquals(expected, actual);
	}
}

public class AppUserManager : UserManager<ApplicationUser<Guid>>
{
	public AppUserManager(
		IUserStore<ApplicationUser<Guid>> store,
		IOptions<IdentityOptions> optionsAccessor,
		IPasswordHasher<ApplicationUser<Guid>> passwordHasher,
		IEnumerable<IUserValidator<ApplicationUser<Guid>>> userValidators,
		IEnumerable<IPasswordValidator<ApplicationUser<Guid>>> passwordValidators,
		ILookupNormalizer keyNormalizer,
		IdentityErrorDescriber errors,
		IServiceProvider services,
		ILogger<UserManager<ApplicationUser<Guid>>> logger) :
		base(
			store,
			optionsAccessor,
			passwordHasher,
			userValidators,
			passwordValidators,
			keyNormalizer,
			errors,
			services,
			logger
		)
	{
	}
}
