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

namespace IamService.Domain.AggregatesModel.AccessReviewAggregate;

/// <summary>
/// EN: Access review aggregate root for periodic access certification.
/// VI: Aggregate root đánh giá truy cập cho chứng nhận truy cập định kỳ.
/// </summary>
public class AccessReview : Entity, IAggregateRoot
{
    private string _name = null!;
    private string? _description;
    private Guid _ownerId;
    private string _scope = null!;  // e.g., "Organization:xxx" or "Role:yyy"
    private AccessReviewStatus _status = null!;
    private DateTime _createdAt;
    private DateTime? _startedAt;
    private DateTime _dueDate;
    private DateTime? _completedAt;

    private readonly List<AccessReviewItem> _items = [];

    #region Properties
    public string Name => _name;
    public string? Description => _description;
    public Guid OwnerId => _ownerId;
    public string Scope => _scope;
    public AccessReviewStatus Status => _status;
    public DateTime CreatedAt => _createdAt;
    public DateTime? StartedAt => _startedAt;
    public DateTime DueDate => _dueDate;
    public DateTime? CompletedAt => _completedAt;
    public IReadOnlyCollection<AccessReviewItem> Items => _items.AsReadOnly();
    #endregion

    protected AccessReview() { }

    private AccessReview(string name, string? description, Guid ownerId, string scope, DateTime dueDate)
    {
        Id = Guid.NewGuid();
        _name = name;
        _description = description;
        _ownerId = ownerId;
        _scope = scope;
        _status = AccessReviewStatus.Draft;
        _createdAt = DateTime.UtcNow;
        _dueDate = dueDate;

        AddDomainEvent(new AccessReviewCreatedEvent(Id, name, ownerId, scope));
    }

    public static AccessReview Create(string name, string? description, Guid ownerId, string scope, DateTime dueDate)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException("Name cannot be empty", nameof(name));
        if (ownerId == Guid.Empty)
            throw new ArgumentException("Owner ID cannot be empty", nameof(ownerId));
        if (dueDate <= DateTime.UtcNow)
            throw new ArgumentException("Due date must be in the future", nameof(dueDate));

        return new AccessReview(name, description, ownerId, scope, dueDate);
    }

    public AccessReviewItem AddItem(Guid userId, string resourceType, Guid resourceId, string permission)
    {
        if (_status != AccessReviewStatus.Draft)
            throw new InvalidOperationException("Can only add items to draft reviews");

        var item = new AccessReviewItem(Id, userId, resourceType, resourceId, permission);
        _items.Add(item);
        return item;
    }

    public void Start()
    {
        if (_status != AccessReviewStatus.Draft)
            throw new InvalidOperationException("Only draft reviews can be started");
        if (_items.Count == 0)
            throw new InvalidOperationException("Review must have at least one item");

        _status = AccessReviewStatus.Active;
        _startedAt = DateTime.UtcNow;

        AddDomainEvent(new AccessReviewStartedEvent(Id, _ownerId, _items.Count));
    }

    public void ReviewItem(Guid itemId, Guid reviewerUserId, bool certify, string? comments = null)
    {
        if (_status != AccessReviewStatus.Active)
            throw new InvalidOperationException("Can only review items in active reviews");

        var item = _items.FirstOrDefault(i => i.Id == itemId)
            ?? throw new InvalidOperationException("Item not found");

        if (certify)
            item.Certify(reviewerUserId, comments);
        else
            item.Revoke(reviewerUserId, comments);
    }

    public void Complete()
    {
        if (_status != AccessReviewStatus.Active)
            throw new InvalidOperationException("Only active reviews can be completed");

        var pendingItems = _items.Count(i => i.Decision == ReviewDecision.Pending);
        if (pendingItems > 0)
            throw new InvalidOperationException($"Cannot complete review with {pendingItems} pending items");

        _status = AccessReviewStatus.Completed;
        _completedAt = DateTime.UtcNow;

        var certifiedCount = _items.Count(i => i.Decision == ReviewDecision.Certify);
        var revokedCount = _items.Count(i => i.Decision == ReviewDecision.Revoke);

        AddDomainEvent(new AccessReviewCompletedEvent(Id, certifiedCount, revokedCount));
    }

    public void Cancel()
    {
        if (_status == AccessReviewStatus.Completed)
            throw new InvalidOperationException("Cannot cancel completed review");

        _status = AccessReviewStatus.Cancelled;
    }
}
