using Books.Api.Domain.Orders.Events;
using EventFlow.Aggregates;

namespace Books.Api.Domain.Orders;

public class OrderAggregate(OrderId id) : AggregateRoot<OrderAggregate, OrderId>(id),
    IEmit<OrderCreatedEvent>,
    IEmit<OrderUpdatedEvent>,
    IEmit<OrderLineAddedEvent>,
    IEmit<OrderLineUpdatedEvent>,
    IEmit<OrderLineRemovedEvent>,
    IEmit<OrderConfirmedEvent>,
    IEmit<OrderRevertedToDraftEvent>,
    IEmit<OrderLinesInvoicedEvent>,
    IEmit<OrderCompletedEvent>,
    IEmit<OrderCancelledEvent>
{
    private bool _isCreated;
    private OrderStatus _status = OrderStatus.Draft;
    private readonly List<OrderLine> _lines = [];
    private string _customerId = string.Empty;
    private decimal _amountExVat;
    private decimal _amountVat;
    private decimal _amountTotal;

    // Expose read-only state for command handlers
    public OrderStatus Status => _status;
    public IReadOnlyList<OrderLine> Lines => _lines.AsReadOnly();
    public string CustomerId => _customerId;
    public decimal AmountExVat => _amountExVat;
    public decimal AmountVat => _amountVat;
    public decimal AmountTotal => _amountTotal;

    /// <summary>
    /// Count of lines that have not been invoiced yet.
    /// </summary>
    public int UninvoicedLineCount => _lines.Count(l => !l.IsInvoiced);

    /// <summary>
    /// Total amount of uninvoiced lines.
    /// </summary>
    public decimal UninvoicedAmount => _lines.Where(l => !l.IsInvoiced).Sum(l => l.AmountTotal);

    #region Apply Methods

    public void Apply(OrderCreatedEvent e)
    {
        _isCreated = true;
        _status = OrderStatus.Draft;
        _customerId = e.CustomerId;
    }

    public void Apply(OrderUpdatedEvent e)
    {
        // Header updates don't change aggregate state that affects business rules
    }

    public void Apply(OrderLineAddedEvent e)
    {
        var line = new OrderLine
        {
            LineNumber = e.LineNumber,
            Description = e.Description,
            Quantity = e.Quantity,
            Unit = e.Unit,
            UnitPrice = e.UnitPrice,
            DiscountPercent = e.DiscountPercent,
            VatCode = e.VatCode,
            AccountId = e.AccountId,
            ProductId = e.ProductId,
            IsInvoiced = false
        };
        _lines.Add(line);
    }

    public void Apply(OrderLineUpdatedEvent e)
    {
        var existingIndex = _lines.FindIndex(l => l.LineNumber == e.LineNumber);
        if (existingIndex >= 0)
        {
            var existing = _lines[existingIndex];
            _lines[existingIndex] = new OrderLine
            {
                LineNumber = e.LineNumber,
                Description = e.Description,
                Quantity = e.Quantity,
                Unit = e.Unit,
                UnitPrice = e.UnitPrice,
                DiscountPercent = e.DiscountPercent,
                VatCode = e.VatCode,
                AccountId = e.AccountId,
                ProductId = e.ProductId,
                IsInvoiced = existing.IsInvoiced,
                InvoiceId = existing.InvoiceId,
                InvoicedAt = existing.InvoicedAt
            };
        }
    }

    public void Apply(OrderLineRemovedEvent e)
    {
        _lines.RemoveAll(l => l.LineNumber == e.LineNumber);
    }

    public void Apply(OrderConfirmedEvent e)
    {
        _status = OrderStatus.Confirmed;
        _amountExVat = e.AmountExVat;
        _amountVat = e.AmountVat;
        _amountTotal = e.AmountTotal;
    }

    public void Apply(OrderRevertedToDraftEvent e)
    {
        _status = OrderStatus.Draft;
    }

    public void Apply(OrderCompletedEvent e)
    {
        // Completed is a terminal state - no additional state changes needed
    }

    public void Apply(OrderLinesInvoicedEvent e)
    {
        _status = e.NewStatus;
        foreach (var lineNumber in e.LineNumbers)
        {
            var index = _lines.FindIndex(l => l.LineNumber == lineNumber);
            if (index >= 0)
            {
                _lines[index] = _lines[index].MarkAsInvoiced(e.InvoiceId, e.InvoicedAt);
            }
        }
    }

    public void Apply(OrderCancelledEvent e)
    {
        _status = OrderStatus.Cancelled;
    }

    #endregion

    #region Command Methods

    public void Create(
        string companyId,
        string fiscalYearId,
        string customerId,
        string customerName,
        string customerNumber,
        string orderNumber,
        DateOnly orderDate,
        DateOnly? expectedDeliveryDate,
        string currency,
        string? vatCode,
        string? notes,
        string? reference,
        string createdBy)
    {
        if (_isCreated)
            throw new DomainException("ORDER_EXISTS", "Order already exists", "Ordre eksisterer allerede");

        if (string.IsNullOrWhiteSpace(companyId))
            throw new DomainException("COMPANY_REQUIRED", "Company ID is required", "Virksomheds-ID er påkrævet");

        if (string.IsNullOrWhiteSpace(customerId))
            throw new DomainException("CUSTOMER_REQUIRED", "Customer ID is required", "Kunde-ID er påkrævet");

        if (string.IsNullOrWhiteSpace(orderNumber))
            throw new DomainException("ORDER_NUMBER_REQUIRED", "Order number is required", "Ordrenummer er påkrævet");

        Emit(new OrderCreatedEvent(
            companyId,
            fiscalYearId,
            customerId,
            customerName,
            customerNumber,
            orderNumber,
            orderDate,
            expectedDeliveryDate,
            currency,
            vatCode,
            notes,
            reference,
            createdBy));
    }

    public void Update(
        DateOnly? expectedDeliveryDate,
        string? notes,
        string? reference,
        string updatedBy)
    {
        if (!_isCreated)
            throw new DomainException("ORDER_NOT_FOUND", "Order does not exist", "Ordre findes ikke");

        if (!_status.CanModify())
            throw new DomainException("ORDER_NOT_MODIFIABLE", $"Cannot modify order in status {_status}", $"Kan ikke ændre ordre med status {_status}");

        Emit(new OrderUpdatedEvent(
            expectedDeliveryDate,
            notes?.Trim(),
            reference?.Trim(),
            updatedBy));
    }

    public void AddLine(
        string description,
        decimal quantity,
        decimal unitPrice,
        string vatCode,
        string? accountId = null,
        string? unit = null,
        decimal discountPercent = 0,
        string? productId = null)
    {
        if (!_isCreated)
            throw new DomainException("ORDER_NOT_FOUND", "Order does not exist", "Ordre findes ikke");

        if (!_status.CanModify())
            throw new DomainException("ORDER_NOT_MODIFIABLE", $"Cannot modify order in status {_status}", $"Kan ikke ændre ordre med status {_status}");

        if (string.IsNullOrWhiteSpace(description))
            throw new DomainException("DESCRIPTION_REQUIRED", "Description is required", "Beskrivelse er påkrævet");

        if (quantity <= 0)
            throw new DomainException("INVALID_QUANTITY", "Quantity must be positive", "Antal skal være positivt");

        if (unitPrice < 0)
            throw new DomainException("INVALID_UNIT_PRICE", "Unit price cannot be negative", "Stykpris kan ikke være negativ");

        var lineNumber = _lines.Count > 0 ? _lines.Max(l => l.LineNumber) + 1 : 1;

        Emit(new OrderLineAddedEvent(
            lineNumber,
            description.Trim(),
            quantity,
            unit,
            unitPrice,
            discountPercent,
            vatCode,
            accountId,
            productId));
    }

    public void UpdateLine(
        int lineNumber,
        string description,
        decimal quantity,
        decimal unitPrice,
        string vatCode,
        string? accountId = null,
        string? unit = null,
        decimal discountPercent = 0,
        string? productId = null)
    {
        if (!_isCreated)
            throw new DomainException("ORDER_NOT_FOUND", "Order does not exist", "Ordre findes ikke");

        if (!_status.CanModify())
            throw new DomainException("ORDER_NOT_MODIFIABLE", $"Cannot modify order in status {_status}", $"Kan ikke ændre ordre med status {_status}");

        var line = _lines.FirstOrDefault(l => l.LineNumber == lineNumber);
        if (line == null)
            throw new DomainException("LINE_NOT_FOUND", $"Line {lineNumber} not found", $"Linje {lineNumber} findes ikke");

        if (line.IsInvoiced)
            throw new DomainException("LINE_ALREADY_INVOICED", $"Line {lineNumber} has already been invoiced", $"Linje {lineNumber} er allerede faktureret");

        Emit(new OrderLineUpdatedEvent(
            lineNumber,
            description.Trim(),
            quantity,
            unit,
            unitPrice,
            discountPercent,
            vatCode,
            accountId,
            productId));
    }

    public void RemoveLine(int lineNumber)
    {
        if (!_isCreated)
            throw new DomainException("ORDER_NOT_FOUND", "Order does not exist", "Ordre findes ikke");

        if (!_status.CanModify())
            throw new DomainException("ORDER_NOT_MODIFIABLE", $"Cannot modify order in status {_status}", $"Kan ikke ændre ordre med status {_status}");

        var line = _lines.FirstOrDefault(l => l.LineNumber == lineNumber);
        if (line == null)
            throw new DomainException("LINE_NOT_FOUND", $"Line {lineNumber} not found", $"Linje {lineNumber} findes ikke");

        if (line.IsInvoiced)
            throw new DomainException("LINE_ALREADY_INVOICED", $"Line {lineNumber} has already been invoiced", $"Linje {lineNumber} er allerede faktureret");

        Emit(new OrderLineRemovedEvent(lineNumber));
    }

    public void Confirm(string confirmedBy)
    {
        if (!_isCreated)
            throw new DomainException("ORDER_NOT_FOUND", "Order does not exist", "Ordre findes ikke");

        if (!_status.CanConfirm())
            throw new DomainException("ORDER_NOT_CONFIRMABLE", $"Cannot confirm order in status {_status}", $"Kan ikke bekræfte ordre med status {_status}");

        if (_lines.Count == 0)
            throw new DomainException("NO_LINES", "Order must have at least one line", "Ordre skal have mindst én linje");

        var amountExVat = _lines.Sum(l => l.AmountExVat);
        var amountVat = _lines.Sum(l => l.AmountVat);
        var amountTotal = _lines.Sum(l => l.AmountTotal);

        Emit(new OrderConfirmedEvent(
            confirmedBy,
            DateTimeOffset.UtcNow,
            amountExVat,
            amountVat,
            amountTotal));
    }

    public void RevertToDraft(string revertedBy, string? reason = null)
    {
        if (!_isCreated)
            throw new DomainException("ORDER_NOT_FOUND", "Order does not exist", "Ordre findes ikke");

        if (_status != OrderStatus.Confirmed)
            throw new DomainException("ORDER_NOT_CONFIRMED", "Only confirmed orders can be reverted to draft", "Kun bekræftede ordrer kan tilbageføres til kladde");

        Emit(new OrderRevertedToDraftEvent(
            revertedBy,
            DateTimeOffset.UtcNow,
            reason?.Trim()));
    }

    /// <summary>
    /// Mark specified lines as invoiced.
    /// Called after invoice is created from this order.
    /// </summary>
    public void MarkLinesAsInvoiced(
        string invoiceId,
        string invoiceNumber,
        IReadOnlyList<int> lineNumbers,
        string invoicedBy)
    {
        if (!_isCreated)
            throw new DomainException("ORDER_NOT_FOUND", "Order does not exist", "Ordre findes ikke");

        if (!_status.CanInvoice())
            throw new DomainException("ORDER_NOT_INVOICEABLE", $"Cannot invoice order in status {_status}", $"Kan ikke fakturere ordre med status {_status}");

        if (lineNumbers.Count == 0)
            throw new DomainException("NO_LINES_SELECTED", "At least one line must be selected for invoicing", "Mindst én linje skal vælges til fakturering");

        // Validate all lines exist and are not already invoiced
        foreach (var lineNumber in lineNumbers)
        {
            var line = _lines.FirstOrDefault(l => l.LineNumber == lineNumber);
            if (line == null)
                throw new DomainException("LINE_NOT_FOUND", $"Line {lineNumber} not found", $"Linje {lineNumber} findes ikke");

            if (line.IsInvoiced)
                throw new DomainException("LINE_ALREADY_INVOICED", $"Line {lineNumber} has already been invoiced", $"Linje {lineNumber} er allerede faktureret");
        }

        // Determine new status
        var uninvoicedAfter = _lines.Count(l => !l.IsInvoiced && !lineNumbers.Contains(l.LineNumber));
        var newStatus = uninvoicedAfter == 0 ? OrderStatus.FullyInvoiced : OrderStatus.PartiallyInvoiced;

        Emit(new OrderLinesInvoicedEvent(
            invoiceId,
            invoiceNumber,
            lineNumbers,
            DateTimeOffset.UtcNow,
            invoicedBy,
            newStatus));
    }

    public void Complete(string completedBy)
    {
        if (!_isCreated)
            throw new DomainException("ORDER_NOT_FOUND", "Order does not exist", "Ordre findes ikke");

        if (_status != OrderStatus.FullyInvoiced)
            throw new DomainException("ORDER_NOT_FULLY_INVOICED", "Only fully invoiced orders can be completed", "Kun fuldt fakturerede ordrer kan markeres som afsluttet");

        Emit(new OrderCompletedEvent(
            completedBy,
            DateTimeOffset.UtcNow));
    }

    public void Cancel(string reason, string cancelledBy)
    {
        if (!_isCreated)
            throw new DomainException("ORDER_NOT_FOUND", "Order does not exist", "Ordre findes ikke");

        if (!_status.CanCancel())
            throw new DomainException("CANNOT_CANCEL", $"Cannot cancel order in status {_status}", $"Kan ikke annullere ordre med status {_status}");

        if (string.IsNullOrWhiteSpace(reason))
            throw new DomainException("REASON_REQUIRED", "Cancellation reason is required", "Annulleringsårsag er påkrævet");

        Emit(new OrderCancelledEvent(
            reason.Trim(),
            cancelledBy,
            DateTimeOffset.UtcNow));
    }

    #endregion
}
