using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using adas_core.Domain.Models.GroupedObservations;
using MongoDB.Bson;

namespace adas_core.Domain.Utils;

/// <summary>
/// Provides a static container for cryptographic operations and utilities related to the Adas domain.
/// </summary>
public static class CryptoAdas
{
    private static readonly Regex PassRegex = new("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$");

    /// <summary>
    /// Computes the MD5 hash of the specified input string and returns it as an uppercase hexadecimal string.
    /// The hash bytes are iterated in reverse order before being formatted.
    /// </summary>
    /// <param name="input">The string to hash. Its ASCII byte representation is used as the input to the MD5 algorithm.</param>
    /// <returns>The MD5 hash of <paramref name="input"/> formatted as an uppercase hexadecimal string.</returns>
    public static string CreateMd5(string input)
        {
            using var md5 = MD5.Create();
            var inputBytes = Encoding.ASCII.GetBytes(input);
            var hashBytes = md5.ComputeHash(inputBytes);
    
            var sb = new StringBuilder();
            for (var i = hashBytes.Length - 1; i >= 0; i--) sb.Append(hashBytes[i].ToString("X2"));
    
            var res = sb.ToString();
            return res;
        }

    /// <summary>
    /// Creates a deterministic MD5 hash that uniquely identifies a grouped field configuration for a specific patient.
    /// Falls back to a single-element list containing the group's name (or empty string) when the <see cref="GroupedField.Names"/> collection is null or empty.
    /// </summary>
    /// <param name="gF">The grouped field whose names, max, since, regularity, and results are included in the hash input.</param>
    /// <param name="patientId">The identifier of the patient whose data is being hashed.</param>
    /// <returns>An MD5 hash string representing the combined patient and grouped field data.</returns>
    public static string CreateMd5GroupedObs(GroupedField gF, ObjectId patientId)
        {
            var names = CollectionsUtils.IfEmptyOrNull(gF.Names, [gF.Name ?? string.Empty]);
    
            return CreateMd5(patientId +
                             string.Join("+", names.Select(c => c.ToString())) +
                             gF.Max +
                             gF.Since +
                             gF.Regularity +
                             string.Join("+", gF.Result.Select(c => c.ToString())));
        }

    /// <summary>
    /// Creates a BCrypt hash from the provided input string, typically used for securely storing passwords.
    /// </summary>
    /// <param name="input">The plain text string to be hashed.</param>
    /// <returns>A BCrypt hashed representation of the input string.</returns>
    public static string CreateBCrypt(string input)
        {
            return BCrypt.Net.BCrypt.HashPassword(input);
        }

    /// <summary>
    /// Verifies that the provided plain text password matches the stored BCrypt password hash.
    /// </summary>
    /// <param name="loginPass">The plain text password entered by the user during login.</param>
    /// <param name="passwordHash">The stored BCrypt hash to compare the password against.</param>
    /// <returns><c>true</c> if the password matches the hash; otherwise, <c>false</c>.</returns>
    public static bool VerifyPassword(string loginPass, string passwordHash)
        {
            return BCrypt.Net.BCrypt.Verify(loginPass, passwordHash);
        }

    /// <summary>
    /// Determines whether the specified password meets the strong password criteria defined by the password regex pattern.
    /// </summary>
    /// <param name="password">The password string to evaluate against the strong password requirements.</param>
    /// <returns><c>true</c> if the password matches the strong password pattern; otherwise, <c>false</c>.</returns>
    public static bool IsStrongPassword(string password)
        {
            return PassRegex.IsMatch(password);
        }
}