using System.Security.Cryptography;
using System.Text;

namespace Nexus.Api.Services;

public static class PasswordSecurity
{
    private const int Iterations = 210_000;
    private const int SaltSize = 16;
    private const int HashSize = 32;
    private const string Version = "v1";

    public static string Hash(string password)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(password);

        var salt = RandomNumberGenerator.GetBytes(SaltSize);
        var hash = Rfc2898DeriveBytes.Pbkdf2(
            password,
            salt,
            Iterations,
            HashAlgorithmName.SHA256,
            HashSize);

        return string.Join('.', Version, Iterations, Convert.ToBase64String(salt), Convert.ToBase64String(hash));
    }

    public static bool Verify(string password, string encodedHash, out bool needsUpgrade)
    {
        needsUpgrade = false;
        if (string.IsNullOrEmpty(password) || string.IsNullOrEmpty(encodedHash)) return false;

        var parts = encodedHash.Split('.');
        if (parts.Length == 4 && parts[0] == Version && int.TryParse(parts[1], out var iterations))
        {
            try
            {
                var salt = Convert.FromBase64String(parts[2]);
                var expected = Convert.FromBase64String(parts[3]);
                var actual = Rfc2898DeriveBytes.Pbkdf2(
                    password,
                    salt,
                    iterations,
                    HashAlgorithmName.SHA256,
                    expected.Length);

                needsUpgrade = iterations < Iterations;
                return CryptographicOperations.FixedTimeEquals(actual, expected);
            }
            catch (FormatException)
            {
                return false;
            }
        }

        if (encodedHash.Length == 64 && encodedHash.All(Uri.IsHexDigit))
        {
            var legacy = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(password)));
            needsUpgrade = true;
            return CryptographicOperations.FixedTimeEquals(
                Encoding.ASCII.GetBytes(legacy),
                Encoding.ASCII.GetBytes(encodedHash.ToUpperInvariant()));
        }

        return false;
    }
}
