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

namespace Eis.Infrastructure.Security;

public sealed class DocumentVerificationCodeService(DocumentVerificationOptions options)
{
    public string ScoreReportCode(
        string registrationId,
        string userId,
        string examId,
        IEnumerable<ScoreSignatureItem> results)
    {
        var scores = results
            .OrderBy(item => item.SubjectId, StringComparer.Ordinal)
            .Select(item => $"{item.SubjectId}:{FormatNumber(item.Score)}:{item.PublishedAt ?? item.UpdatedAt ?? string.Empty}");
        return $"SR-{Signature("score-report", [registrationId, userId, examId, .. scores])}";
    }

    public string AdmissionNoticeCode(
        string placementId,
        string userId,
        string schoolId,
        string examId,
        string categoryCode,
        string noticeNumber,
        string updatedAt) =>
        $"AN-{Signature("admission-notice", [placementId, userId, schoolId, examId, categoryCode, noticeNumber, updatedAt])}";

    public static bool SafeEquals(string left, string right)
    {
        var leftBytes = Encoding.UTF8.GetBytes((left ?? string.Empty).ToUpperInvariant());
        var rightBytes = Encoding.UTF8.GetBytes((right ?? string.Empty).ToUpperInvariant());
        return leftBytes.Length == rightBytes.Length && CryptographicOperations.FixedTimeEquals(leftBytes, rightBytes);
    }

    private string Signature(string type, IEnumerable<string> parts)
    {
        var payload = string.Join('\u001f', new[] { type }.Concat(parts));
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(options.Secret));
        var signature = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)));
        return signature[..24];
    }

    private static string FormatNumber(double value) =>
        value.ToString("0.#############################", CultureInfo.InvariantCulture);
}

public sealed record ScoreSignatureItem(
    string SubjectId,
    double Score,
    string? PublishedAt,
    string? UpdatedAt);
