using CMSMicroservice.Application.Common.Interfaces;
using System.Security.Cryptography;
using System.Text;

namespace CMSMicroservice.Infrastructure.Services;
public class HashService : IHashService
{
    // PBKDF2 parameters
    private const int SaltSize = 16; // 128-bit
    private const int KeySize = 32;  // 256-bit
    private const int Iterations = 100_000; // strong enough default
    private const string Pbkdf2Prefix = "PBKDF2"; // version marker

    public string ComputeSha256Hex(string input)
    {
        using var sha = SHA256.Create();
        var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(input));
        return Convert.ToHexString(bytes);
    }

    public bool VerifySha256Hex(string input, string? expectedHexHash)
    {
        if (string.IsNullOrEmpty(expectedHexHash)) return false;
        var actual = ComputeSha256Hex(input);
        return actual.Equals(expectedHexHash, StringComparison.OrdinalIgnoreCase);
    }

    public string ComputeHmacSha256Hex(string input, string secret)
    {
        using var h = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
        var hash = h.ComputeHash(Encoding.UTF8.GetBytes(input));
        return Convert.ToHexString(hash);
    }

    public bool VerifyHmacSha256Hex(string input, string? expectedHexHash, string secret)
    {
        if (string.IsNullOrEmpty(expectedHexHash)) return false;
        var actual = ComputeHmacSha256Hex(input, secret);
        return actual.Equals(expectedHexHash, StringComparison.OrdinalIgnoreCase);
    }

    public string HashPassword(string password)
    {
        // generate salt
        Span<byte> salt = stackalloc byte[SaltSize];
        RandomNumberGenerator.Fill(salt);

        // derive key
        var pbkdf2 = new Rfc2898DeriveBytes(password, salt.ToArray(), Iterations, HashAlgorithmName.SHA256);
        var derived = pbkdf2.GetBytes(KeySize);

        // encode: PBKDF2$<iter>$<saltBase64>$<hashBase64>
        return string.Join('$', Pbkdf2Prefix, Iterations.ToString(), Convert.ToBase64String(salt.ToArray()), Convert.ToBase64String(derived));
    }

    public bool VerifyPassword(string password, string? storedHash)
    {
        if (string.IsNullOrWhiteSpace(storedHash)) return false;

        // legacy: raw SHA-256 hex
        if (!storedHash.Contains('$'))
        {
            return VerifySha256Hex(password, storedHash);
        }

        var parts = storedHash.Split('$');
        if (parts.Length != 4 || !string.Equals(parts[0], Pbkdf2Prefix, StringComparison.Ordinal))
            return false;

        if (!int.TryParse(parts[1], out var iterations) || iterations <= 0) return false;
        byte[] salt;
        byte[] expectedKey;
        try
        {
            salt = Convert.FromBase64String(parts[2]);
            expectedKey = Convert.FromBase64String(parts[3]);
        }
        catch
        {
            return false;
        }

        var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256);
        var actualKey = pbkdf2.GetBytes(expectedKey.Length);
        // fixed-time comparison
        return CryptographicOperations.FixedTimeEquals(actualKey, expectedKey);
    }
}
