#nullable enable
using System.Security.Cryptography;
using Baya.Application.Contracts.Common;
using Baya.Application.Models.Identity;
using Baya.Domain.Entities.User;

namespace Baya.Application.Features.Identity;

/// <summary>
/// Shared vocabulary of the identity slices: the `platform_configs` keys the handlers read at compute
/// time (never hardcode the values), refresh-token minting, and phone masking for `/me`-style payloads.
/// </summary>
internal static class IdentityDefaults
{
    /// <summary>Seconds a caller must wait before the same phone can be sent another OTP.</summary>
    public const string OtpResendSecondsKey = "auth_otp_resend_seconds";

    /// <summary>Wrong-code attempts allowed before verification is refused until a fresh OTP.</summary>
    public const string OtpMaxAttemptsKey = "auth_otp_max_attempts";

    /// <summary>Refresh-token session lifetime, in days.</summary>
    public const string SessionTtlDaysKey = "auth_session_ttl_days";

    /// <summary>Number of digits in the OTP code — mirrors the TOTP token provider (6). Surfaced on
    /// <c>RequestOtpResult</c> so the client renders the correct number of input boxes contract-driven.</summary>
    public const int OtpCodeLength = 6;

    /// <summary>How long an OTP stays valid, in seconds — mirrors the passwordless TOTP
    /// <c>TokenLifespan</c> (1 minute). Surfaced so the client can show a "code expires in …" hint.</summary>
    public const int OtpExpirySeconds = 60;

    /// <summary>Cache key of the per-phone resend window (keyed by phone hash, never the raw phone).</summary>
    public static string OtpResendCacheKey(string phoneHash) => $"auth:otp:resend:{phoneHash}";

    /// <summary>256-bit random opaque refresh token, hex-encoded (URL-safe). Only its keyed hash is stored.</summary>
    public static string NewRefreshToken() => Convert.ToHexString(RandomNumberGenerator.GetBytes(32));

    /// <summary>Mints a raw refresh token plus its <c>user_sessions</c> row (hash stored, never the token).</summary>
    public static (string RefreshToken, UserSession Session) MintSession(
        IFieldEncryptor fieldEncryptor,
        int userId,
        DateTimeOffset now,
        int ttlDays,
        string? deviceInfo,
        string? ipAddress)
    {
        var refreshToken = NewRefreshToken();

        var session = new UserSession
        {
            UserId = userId,
            RefreshTokenHash = fieldEncryptor.Hash(refreshToken),
            DeviceInfo = deviceInfo,
            IpAddress = ipAddress,
            IsRevoked = false,
            ExpiresAt = now.AddDays(ttlDays)
        };

        return (refreshToken, session);
    }

    /// <summary>Masks all but the first four and last two digits (e.g. 0912*****89).</summary>
    public static string MaskPhone(string? phone)
    {
        if (string.IsNullOrEmpty(phone))
            return string.Empty;

        return phone.Length <= 6
            ? new string('*', phone.Length)
            : $"{phone[..4]}{new string('*', phone.Length - 6)}{phone[^2..]}";
    }

    public static MeResult ToMeResult(UserAccountSnapshot snapshot) =>
        new(
            snapshot.Id,
            MaskPhone(snapshot.Phone),
            snapshot.FirstName,
            snapshot.LastName,
            snapshot.Gender,
            snapshot.IsActive,
            snapshot.Roles,
            HasCustomerProfile: false,
            HasNurseProfile: false,
            NurseVerificationStatus: MeResult.VerificationNotStarted);
}