using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
using static System.Security.Cryptography.RandomNumberGenerator;

namespace Femto.Modules.Auth.Models;

public class LongTermSession
{
    private static TimeSpan TokenTimeout { get; } = TimeSpan.FromDays(90);
    private static TimeSpan RefreshBuffer { get; } = TimeSpan.FromDays(5);

    public int Id { get; private set; }

    public string Selector { get; private set; }

    public byte[] HashedVerifier { get; private set; }

    public DateTimeOffset Expires { get; private set; }

    public Guid UserId { get; private set; }
    
    public bool IsInvalidated { get; private set; }

    [NotMapped]
    public bool ExpiresSoon => this.Expires < DateTimeOffset.UtcNow + RefreshBuffer;

    private LongTermSession() { }

    public static (LongTermSession, string) Create(Guid userId)
    {
        var selector = GetString(
            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
            12
        );
     
        var verifier = GetString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 32);

        using var sha256 = System.Security.Cryptography.SHA256.Create();

        var longTermSession = new LongTermSession
        {
            Selector = selector,
            HashedVerifier = ComputeHash(verifier),
            UserId = userId,
            Expires = DateTimeOffset.UtcNow + TokenTimeout,
        };

        return (longTermSession, verifier);
    }

    public bool CheckVerifier(string verifier)
    {
        if (this.IsInvalidated)
            return false;
        
        if (this.Expires < DateTimeOffset.UtcNow)
            return false;

        return ComputeHash(verifier).SequenceEqual(this.HashedVerifier);
    }

    private static byte[] ComputeHash(string verifier)
    {
        using var sha256 = System.Security.Cryptography.SHA256.Create();
        var hashedVerifier = sha256.ComputeHash(Encoding.UTF8.GetBytes(verifier));
        return hashedVerifier;
    }
    
    public void Invalidate()
    {
        this.IsInvalidated = true;
    }
}
