// EN: Inventory item aggregate root.
// VI: Aggregate root InventoryItem.

using InventoryService.Domain.Events;
using InventoryService.Domain.Exceptions;
using InventoryService.Domain.SeedWork;

namespace InventoryService.Domain.AggregatesModel.InventoryAggregate;

/// <summary>
/// EN: Inventory item aggregate root - manages stock levels.
/// VI: Aggregate root InventoryItem - quản lý mức tồn kho.
/// </summary>
public class InventoryItem : Entity, IAggregateRoot
{
    private Guid _productId;
    private Guid _shopId;
    private int _quantity;
    private int _reservedQuantity;
    private int _reorderLevel;
    private DateTime _createdAt;
    private DateTime? _updatedAt;

    private readonly List<InventoryTransaction> _transactions = new();

    /// <summary>
    /// EN: Product ID from Catalog Service.
    /// VI: ID sản phẩm từ Catalog Service.
    /// </summary>
    public Guid ProductId => _productId;

    /// <summary>
    /// EN: Shop ID that owns this inventory.
    /// VI: ID shop sở hữu inventory này.
    /// </summary>
    public Guid ShopId => _shopId;

    /// <summary>
    /// EN: Total quantity in stock.
    /// VI: Tổng số lượng trong kho.
    /// </summary>
    public int Quantity => _quantity;

    /// <summary>
    /// EN: Reserved quantity for orders.
    /// VI: Số lượng đặt trước cho đơn hàng.
    /// </summary>
    public int ReservedQuantity => _reservedQuantity;

    /// <summary>
    /// EN: Available quantity (total - reserved).
    /// VI: Số lượng khả dụng (tổng - đặt trước).
    /// </summary>
    public int AvailableQuantity => _quantity - _reservedQuantity;

    /// <summary>
    /// EN: Reorder level - alert when stock goes below this.
    /// VI: Mức đặt hàng lại - cảnh báo khi tồn kho dưới mức này.
    /// </summary>
    public int ReorderLevel => _reorderLevel;

    /// <summary>
    /// EN: Transaction history.
    /// VI: Lịch sử giao dịch.
    /// </summary>
    public IReadOnlyCollection<InventoryTransaction> Transactions => _transactions.AsReadOnly();

    /// <summary>
    /// EN: Creation timestamp.
    /// VI: Thời gian tạo.
    /// </summary>
    public DateTime CreatedAt => _createdAt;

    /// <summary>
    /// EN: Last update timestamp.
    /// VI: Thời gian cập nhật cuối.
    /// </summary>
    public DateTime? UpdatedAt => _updatedAt;

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

    /// <summary>
    /// EN: Create a new inventory item.
    /// VI: Tạo inventory item mới.
    /// </summary>
    public InventoryItem(Guid productId, Guid shopId, int reorderLevel = 10)
    {
        if (productId == Guid.Empty)
            throw new DomainException("Product ID cannot be empty");
        if (shopId == Guid.Empty)
            throw new DomainException("Shop ID cannot be empty");

        Id = Guid.NewGuid();
        _productId = productId;
        _shopId = shopId;
        _quantity = 0;
        _reservedQuantity = 0;
        _reorderLevel = reorderLevel;
        _createdAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Stock in operation.
    /// VI: Thao tác nhập kho.
    /// </summary>
    public void StockIn(int amount, string? notes = null, Guid? referenceId = null)
    {
        if (amount <= 0)
            throw new DomainException("Stock in amount must be positive");

        _quantity += amount;
        _updatedAt = DateTime.UtcNow;

        var transaction = new InventoryTransaction(Id, TransactionType.In, amount, referenceId, notes);
        _transactions.Add(transaction);

        AddDomainEvent(new StockChangedDomainEvent(this));
    }

    /// <summary>
    /// EN: Stock out operation.
    /// VI: Thao tác xuất kho.
    /// </summary>
    public void StockOut(int amount, string? notes = null, Guid? referenceId = null)
    {
        if (amount <= 0)
            throw new DomainException("Stock out amount must be positive");
        if (AvailableQuantity < amount)
            throw new DomainException($"Insufficient stock. Available: {AvailableQuantity}, Requested: {amount}");

        _quantity -= amount;
        _updatedAt = DateTime.UtcNow;

        var transaction = new InventoryTransaction(Id, TransactionType.Out, -amount, referenceId, notes);
        _transactions.Add(transaction);

        AddDomainEvent(new StockChangedDomainEvent(this));
    }

    /// <summary>
    /// EN: Reserve stock for order.
    /// VI: Đặt trước tồn kho cho đơn hàng.
    /// </summary>
    public void Reserve(int amount, Guid orderId)
    {
        if (amount <= 0)
            throw new DomainException("Reserve amount must be positive");
        if (AvailableQuantity < amount)
            throw new DomainException($"Insufficient stock to reserve. Available: {AvailableQuantity}, Requested: {amount}");

        _reservedQuantity += amount;
        _updatedAt = DateTime.UtcNow;

        var transaction = new InventoryTransaction(Id, TransactionType.Reserve, amount, orderId);
        _transactions.Add(transaction);
    }

    /// <summary>
    /// EN: Release reservation.
    /// VI: Giải phóng đặt trước.
    /// </summary>
    public void ReleaseReservation(int amount, Guid orderId)
    {
        if (amount <= 0)
            throw new DomainException("Release amount must be positive");
        if (_reservedQuantity < amount)
            throw new DomainException("Cannot release more than reserved");

        _reservedQuantity -= amount;
        _updatedAt = DateTime.UtcNow;

        var transaction = new InventoryTransaction(Id, TransactionType.Release, -amount, orderId);
        _transactions.Add(transaction);
    }

    /// <summary>
    /// EN: Manual adjustment (stocktake).
    /// VI: Điều chỉnh thủ công (kiểm kê).
    /// </summary>
    public void Adjust(int newQuantity, string notes)
    {
        var diff = newQuantity - _quantity;
        _quantity = newQuantity;
        _updatedAt = DateTime.UtcNow;

        var transaction = new InventoryTransaction(Id, TransactionType.Adjustment, diff, null, notes);
        _transactions.Add(transaction);

        AddDomainEvent(new StockChangedDomainEvent(this));
    }
}
