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

namespace Mirea.Api.Security.Services;

public class PasswordHashService
{
    public int SaltSize { private get; init; }
    public int HashSize { private get; init; }
    public int Iterations { private get; init; }
    public int Memory { private get; init; }
    public int Parallelism { private get; init; }
    public string? Secret { private get; init; }

    private ReadOnlySpan<byte> HashPassword(string password, ReadOnlySpan<byte> salt)
    {
        var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
        {
            Iterations = Iterations,
            MemorySize = Memory,
            DegreeOfParallelism = Parallelism,
            Salt = salt.ToArray()
        };

        if (!string.IsNullOrEmpty(Secret))
            argon2.KnownSecret = Convert.FromBase64String(Secret);

        return argon2.GetBytes(HashSize);
    }

    private static bool ConstantTimeComparison(ReadOnlySpan<byte> a, ReadOnlySpan<byte> b)
    {
        if (a.Length != b.Length)
            return false;

        var result = 0;
        for (var i = 0; i < a.Length; i++)
            result |= a[i] ^ b[i];
        return result == 0;
    }

    public (string Salt, string Hash) HashPassword(string password)
    {
        var salt = GeneratorKey.GenerateBytes(SaltSize);
        var hash = HashPassword(password, salt);

        return (Convert.ToBase64String(salt), Convert.ToBase64String(hash));
    }

    public bool VerifyPassword(string password, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> hash) =>
        ConstantTimeComparison(HashPassword(password, salt), hash);

    public bool VerifyPassword(string password, string saltBase64, string hashBase64) =>
        VerifyPassword(password, Convert.FromBase64String(saltBase64), Convert.FromBase64String(hashBase64));
}