using System.Security.Cryptography;
using IamService.Domain.Events;
using IamService.Domain.SeedWork;

namespace IamService.Domain.AggregatesModel.VerificationAggregate;

/// <summary>
/// EN: Identity verification aggregate root for user verification (email, phone, document).
/// VI: Aggregate root xác thực danh tính cho user (email, phone, tài liệu).
/// </summary>
public class IdentityVerification : Entity, IAggregateRoot
{
    // EN: Constants for verification
    // VI: Hằng số cho xác thực
    private const int MaxAttempts = 5;
    private const int OtpLength = 6;
    private const int OtpExpirationMinutes = 10;
    private const int DocumentExpirationDays = 7;

    // EN: Private fields for encapsulation
    // VI: Fields private để đóng gói
    private Guid _userId;
    private VerificationType _type = null!;
    private VerificationStatus _status = null!;
    private string? _verificationData;  // EN: Encrypted data (phone number, document URL)
    private string? _verificationCodeHash;  // EN: Hashed OTP for phone verification
    private DateTime _requestedAt;
    private DateTime? _verifiedAt;
    private DateTime _expiresAt;
    private int _attemptCount;
    private string? _rejectionReason;
    private string? _metadata;  // EN: JSON metadata



    /// <summary>
    /// EN: User ID this verification belongs to.
    /// VI: ID user mà xác thực này thuộc về.
    /// </summary>
    public Guid UserId => _userId;

    /// <summary>
    /// EN: Type of verification.
    /// VI: Loại xác thực.
    /// </summary>
    public VerificationType Type => _type;

    /// <summary>
    /// EN: Type ID for EF Core mapping.
    /// VI: ID loại cho EF Core mapping.
    /// </summary>
    public int TypeId { get; private set; }

    /// <summary>
    /// EN: Current status of verification.
    /// VI: Trạng thái hiện tại của xác thực.
    /// </summary>
    public VerificationStatus Status => _status;

    /// <summary>
    /// EN: Status ID for EF Core mapping.
    /// VI: ID trạng thái cho EF Core mapping.
    /// </summary>
    public int StatusId { get; private set; }

    /// <summary>
    /// EN: Verification data (encrypted).
    /// VI: Dữ liệu xác thực (đã mã hóa).
    /// </summary>
    public string? VerificationData => _verificationData;

    /// <summary>
    /// EN: Hashed verification code.
    /// VI: Mã xác thực đã hash.
    /// </summary>
    public string? VerificationCodeHash => _verificationCodeHash;

    /// <summary>
    /// EN: When verification was requested.
    /// VI: Thời điểm yêu cầu xác thực.
    /// </summary>
    public DateTime RequestedAt => _requestedAt;

    /// <summary>
    /// EN: When verification was completed.
    /// VI: Thời điểm xác thực hoàn thành.
    /// </summary>
    public DateTime? VerifiedAt => _verifiedAt;

    /// <summary>
    /// EN: When verification expires.
    /// VI: Thời điểm xác thực hết hạn.
    /// </summary>
    public DateTime ExpiresAt => _expiresAt;

    /// <summary>
    /// EN: Number of verification attempts.
    /// VI: Số lần thử xác thực.
    /// </summary>
    public int AttemptCount => _attemptCount;

    /// <summary>
    /// EN: Reason for rejection (if rejected).
    /// VI: Lý do từ chối (nếu bị từ chối).
    /// </summary>
    public string? RejectionReason => _rejectionReason;

    /// <summary>
    /// EN: Additional metadata in JSON format.
    /// VI: Metadata bổ sung dạng JSON.
    /// </summary>
    public string? Metadata => _metadata;


    /// <summary>
    /// EN: Private constructor for EF Core.
    /// VI: Constructor private cho EF Core.
    /// </summary>
    protected IdentityVerification()
    {
    }

    /// <summary>
    /// EN: Create a new identity verification.
    /// VI: Tạo xác thực danh tính mới.
    /// </summary>
    private IdentityVerification(
        Guid userId,
        VerificationType type,
        string? verificationData,
        int expirationMinutes) : this()
    {
        if (userId == Guid.Empty)
            throw new ArgumentException("User ID cannot be empty", nameof(userId));

        Id = Guid.NewGuid();
        _userId = userId;
        _type = type ?? throw new ArgumentNullException(nameof(type));
        TypeId = type.Id;
        _status = VerificationStatus.Pending;
        StatusId = VerificationStatus.Pending.Id;
        _verificationData = verificationData;
        _requestedAt = DateTime.UtcNow;
        _expiresAt = DateTime.UtcNow.AddMinutes(expirationMinutes);
        _attemptCount = 0;
    }

    /// <summary>
    /// EN: Create phone verification with OTP.
    /// VI: Tạo xác thực điện thoại với OTP.
    /// </summary>
    /// <returns>Tuple of (IdentityVerification, plainTextOtp)</returns>
    public static (IdentityVerification Verification, string Otp) CreatePhoneVerification(
        Guid userId,
        string phoneNumber)
    {
        if (string.IsNullOrWhiteSpace(phoneNumber))
            throw new ArgumentException("Phone number cannot be empty", nameof(phoneNumber));

        var verification = new IdentityVerification(
            userId,
            VerificationType.Phone,
            phoneNumber,
            OtpExpirationMinutes);

        // EN: Generate and hash OTP
        // VI: Tạo và hash OTP
        var otp = GenerateOtp();
        verification._verificationCodeHash = HashOtp(otp);

        verification.AddDomainEvent(new VerificationRequestedEvent(
            verification.Id,
            userId,
            VerificationType.Phone.Name));

        return (verification, otp);
    }

    /// <summary>
    /// EN: Create document verification for KYC.
    /// VI: Tạo xác thực tài liệu cho KYC.
    /// </summary>
    public static IdentityVerification CreateDocumentVerification(
        Guid userId,
        string documentUrl,
        string? documentType = null)
    {
        if (string.IsNullOrWhiteSpace(documentUrl))
            throw new ArgumentException("Document URL cannot be empty", nameof(documentUrl));

        var verification = new IdentityVerification(
            userId,
            VerificationType.Document,
            documentUrl,
            DocumentExpirationDays * 24 * 60);

        verification._status = VerificationStatus.InProgress;
        verification.StatusId = VerificationStatus.InProgress.Id;

        if (!string.IsNullOrEmpty(documentType))
        {
            verification._metadata = System.Text.Json.JsonSerializer.Serialize(new
            {
                DocumentType = documentType,
                SubmittedAt = DateTime.UtcNow
            });
        }

        verification.AddDomainEvent(new VerificationRequestedEvent(
            verification.Id,
            userId,
            VerificationType.Document.Name));

        return verification;
    }

    /// <summary>
    /// EN: Verify OTP code.
    /// VI: Xác minh mã OTP.
    /// </summary>
    public bool VerifyCode(string code)
    {
        if (_status.IsFinal)
            throw new InvalidOperationException($"Verification is already in final status: {_status.Name}");

        if (DateTime.UtcNow > _expiresAt)
        {
            MarkAsExpired();
            return false;
        }

        _attemptCount++;

        if (_attemptCount > MaxAttempts)
        {
            _status = VerificationStatus.Rejected;
            StatusId = VerificationStatus.Rejected.Id;
            _rejectionReason = "Maximum attempts exceeded";
            return false;
        }

        var isValid = VerifyOtpHash(code, _verificationCodeHash);

        if (isValid)
        {
            MarkAsVerified();
        }

        return isValid;
    }

    /// <summary>
    /// EN: Mark verification as verified.
    /// VI: Đánh dấu xác thực hoàn thành.
    /// </summary>
    public void MarkAsVerified()
    {
        if (_status.IsFinal)
            throw new InvalidOperationException($"Verification is already in final status: {_status.Name}");

        _status = VerificationStatus.Verified;
        StatusId = VerificationStatus.Verified.Id;
        _verifiedAt = DateTime.UtcNow;

        AddDomainEvent(new VerificationCompletedEvent(Id, _userId, _type.Name, true));
    }

    /// <summary>
    /// EN: Mark verification as rejected.
    /// VI: Đánh dấu xác thực bị từ chối.
    /// </summary>
    public void MarkAsRejected(string reason)
    {
        if (_status.IsFinal)
            throw new InvalidOperationException($"Verification is already in final status: {_status.Name}");

        _status = VerificationStatus.Rejected;
        StatusId = VerificationStatus.Rejected.Id;
        _rejectionReason = reason;

        AddDomainEvent(new VerificationCompletedEvent(Id, _userId, _type.Name, false, reason));
    }

    /// <summary>
    /// EN: Mark verification as expired.
    /// VI: Đánh dấu xác thực hết hạn.
    /// </summary>
    public void MarkAsExpired()
    {
        if (_status.IsFinal)
            return;

        _status = VerificationStatus.Expired;
        StatusId = VerificationStatus.Expired.Id;
    }

    /// <summary>
    /// EN: Cancel the verification.
    /// VI: Hủy xác thực.
    /// </summary>
    public void Cancel()
    {
        if (_status.IsFinal)
            throw new InvalidOperationException($"Verification is already in final status: {_status.Name}");

        _status = VerificationStatus.Cancelled;
        StatusId = VerificationStatus.Cancelled.Id;
    }

    /// <summary>
    /// EN: Check if verification is expired.
    /// VI: Kiểm tra xác thực đã hết hạn chưa.
    /// </summary>
    public bool IsExpired => DateTime.UtcNow > _expiresAt;

    /// <summary>
    /// EN: Check if can retry verification.
    /// VI: Kiểm tra có thể thử lại xác thực không.
    /// </summary>
    public bool CanRetry => _attemptCount < MaxAttempts && !_status.IsFinal && !IsExpired;


    /// <summary>
    /// EN: Generate a random OTP.
    /// VI: Tạo OTP ngẫu nhiên.
    /// </summary>
    private static string GenerateOtp()
    {
        var bytes = RandomNumberGenerator.GetBytes(4);
        var number = BitConverter.ToUInt32(bytes, 0) % (uint)Math.Pow(10, OtpLength);
        return number.ToString().PadLeft(OtpLength, '0');
    }

    /// <summary>
    /// EN: Hash OTP for storage.
    /// VI: Hash OTP để lưu trữ.
    /// </summary>
    private static string HashOtp(string otp)
    {
        var bytes = System.Text.Encoding.UTF8.GetBytes(otp);
        var hash = SHA256.HashData(bytes);
        return Convert.ToBase64String(hash);
    }

    /// <summary>
    /// EN: Verify OTP against hash.
    /// VI: Xác minh OTP với hash.
    /// </summary>
    private static bool VerifyOtpHash(string otp, string? hash)
    {
        if (string.IsNullOrEmpty(hash))
            return false;

        var computedHash = HashOtp(otp);
        return computedHash == hash;
    }
}
