using IamService.Domain.Events;
using IamService.Domain.SeedWork;

namespace IamService.Domain.AggregatesModel.PrivilegedAccessAggregate;

/// <summary>
/// EN: Privileged access grant aggregate for Just-In-Time (JIT) elevated access.
/// VI: Aggregate cấp quyền truy cập đặc quyền cho truy cập nâng cao tức thời (JIT).
/// </summary>
public class PrivilegedAccessGrant : Entity, IAggregateRoot
{
    private Guid _userId;
    private Guid _roleId;
    private string _resourceScope = null!;  // e.g., "Organization:xxx" or "*"
    private string? _reason;
    private Guid _grantedByUserId;
    private PrivilegedAccessStatus _status = null!;
    private DateTime _createdAt;
    private DateTime _startsAt;
    private DateTime _expiresAt;
    private DateTime? _revokedAt;
    private Guid? _revokedByUserId;
    private string? _revocationReason;

    #region Properties
    public Guid UserId => _userId;
    public Guid RoleId => _roleId;
    public string ResourceScope => _resourceScope;
    public string? Reason => _reason;
    public Guid GrantedByUserId => _grantedByUserId;
    public PrivilegedAccessStatus Status => _status;
    public DateTime CreatedAt => _createdAt;
    public DateTime StartsAt => _startsAt;
    public DateTime ExpiresAt => _expiresAt;
    public DateTime? RevokedAt => _revokedAt;
    public Guid? RevokedByUserId => _revokedByUserId;
    public string? RevocationReason => _revocationReason;
    public bool IsActive => _status == PrivilegedAccessStatus.Active && DateTime.UtcNow >= _startsAt && DateTime.UtcNow < _expiresAt;
    #endregion

    protected PrivilegedAccessGrant() { }

    private PrivilegedAccessGrant(
        Guid userId,
        Guid roleId,
        string resourceScope,
        string? reason,
        Guid grantedByUserId,
        DateTime startsAt,
        DateTime expiresAt)
    {
        Id = Guid.NewGuid();
        _userId = userId;
        _roleId = roleId;
        _resourceScope = resourceScope;
        _reason = reason;
        _grantedByUserId = grantedByUserId;
        _status = startsAt <= DateTime.UtcNow ? PrivilegedAccessStatus.Active : PrivilegedAccessStatus.Pending;
        _createdAt = DateTime.UtcNow;
        _startsAt = startsAt;
        _expiresAt = expiresAt;

        AddDomainEvent(new PrivilegedAccessGrantedEvent(Id, userId, roleId, resourceScope, expiresAt));
    }

    public static PrivilegedAccessGrant Create(
        Guid userId,
        Guid roleId,
        string resourceScope,
        string? reason,
        Guid grantedByUserId,
        int durationMinutes = 60)
    {
        if (userId == Guid.Empty)
            throw new ArgumentException("User ID cannot be empty", nameof(userId));
        if (roleId == Guid.Empty)
            throw new ArgumentException("Role ID cannot be empty", nameof(roleId));
        if (string.IsNullOrWhiteSpace(resourceScope))
            throw new ArgumentException("Resource scope cannot be empty", nameof(resourceScope));
        if (durationMinutes < 5 || durationMinutes > 480)
            throw new ArgumentException("Duration must be between 5 and 480 minutes", nameof(durationMinutes));

        var startsAt = DateTime.UtcNow;
        var expiresAt = startsAt.AddMinutes(durationMinutes);

        return new PrivilegedAccessGrant(userId, roleId, resourceScope, reason, grantedByUserId, startsAt, expiresAt);
    }

    public static PrivilegedAccessGrant CreateScheduled(
        Guid userId,
        Guid roleId,
        string resourceScope,
        string? reason,
        Guid grantedByUserId,
        DateTime startsAt,
        DateTime expiresAt)
    {
        if (startsAt >= expiresAt)
            throw new ArgumentException("Start time must be before expiration time");
        if (expiresAt <= DateTime.UtcNow)
            throw new ArgumentException("Expiration time must be in the future");

        return new PrivilegedAccessGrant(userId, roleId, resourceScope, reason, grantedByUserId, startsAt, expiresAt);
    }

    public void Activate()
    {
        if (_status != PrivilegedAccessStatus.Pending)
            throw new InvalidOperationException("Only pending grants can be activated");

        _status = PrivilegedAccessStatus.Active;
    }

    public void Revoke(Guid revokedByUserId, string? reason = null)
    {
        if (_status.IsTerminal)
            throw new InvalidOperationException("Grant is already terminated");

        _status = PrivilegedAccessStatus.Revoked;
        _revokedAt = DateTime.UtcNow;
        _revokedByUserId = revokedByUserId;
        _revocationReason = reason;

        AddDomainEvent(new PrivilegedAccessRevokedEvent(Id, _userId, revokedByUserId, reason));
    }

    public void Expire()
    {
        if (_status.IsTerminal)
            throw new InvalidOperationException("Grant is already terminated");

        _status = PrivilegedAccessStatus.Expired;
    }

    public void Extend(int additionalMinutes, Guid extendedByUserId)
    {
        if (_status.IsTerminal)
            throw new InvalidOperationException("Cannot extend terminated grant");
        if (additionalMinutes < 5 || additionalMinutes > 240)
            throw new ArgumentException("Extension must be between 5 and 240 minutes");

        _expiresAt = _expiresAt.AddMinutes(additionalMinutes);
    }
}
