using CashLineERP.Domain.Common;

namespace CashLineERP.Domain.Finance;

public sealed class CustomerStatement : Entity
{
    public long Id { get; private set; }

    public string StatementNumber { get; private set; } = string.Empty;

    public long CustomerId { get; private set; }

    public string CustomerCode { get; private set; } = string.Empty;

    public string CustomerName { get; private set; } = string.Empty;

    public DateOnly? DateFrom { get; private set; }

    public DateOnly? DateTo { get; private set; }

    public string CurrencyCode { get; private set; } = "EGP";

    public decimal OpeningBalanceAmount { get; private set; }

    public decimal TotalChargeAmount { get; private set; }

    public decimal TotalSettlementAmount { get; private set; }

    public decimal ClosingBalanceAmount { get; private set; }

    public decimal OpenInvoiceAmount { get; private set; }

    public decimal OverdueInvoiceAmount { get; private set; }

    public DateTime GeneratedAtUtc { get; private set; }

    public long GeneratedByUserId { get; private set; }

    public List<CustomerStatementLine> Lines { get; private set; } = [];

    public List<CustomerStatementDeliveryLog> DeliveryLogs { get; private set; } = [];

    public CustomerStatement(
        string statementNumber,
        long customerId,
        string customerCode,
        string customerName,
        DateOnly? dateFrom,
        DateOnly? dateTo,
        string currencyCode,
        decimal openingBalanceAmount,
        decimal totalChargeAmount,
        decimal totalSettlementAmount,
        decimal closingBalanceAmount,
        decimal openInvoiceAmount,
        decimal overdueInvoiceAmount,
        long actorUserId)
    {
        if (string.IsNullOrWhiteSpace(statementNumber))
        {
            throw new InvalidOperationException("Customer statement number is required.");
        }

        if (string.IsNullOrWhiteSpace(customerCode))
        {
            throw new InvalidOperationException("Customer statement requires a customer code snapshot.");
        }

        if (string.IsNullOrWhiteSpace(customerName))
        {
            throw new InvalidOperationException("Customer statement requires a customer name snapshot.");
        }

        if (string.IsNullOrWhiteSpace(currencyCode))
        {
            throw new InvalidOperationException("Customer statement currency is required.");
        }

        if (dateFrom.HasValue && dateTo.HasValue && dateFrom.Value > dateTo.Value)
        {
            throw new InvalidOperationException("Customer statement date range is invalid because dateFrom is after dateTo.");
        }

        StatementNumber = statementNumber.Trim().ToUpperInvariant();
        CustomerId = customerId;
        CustomerCode = customerCode.Trim().ToUpperInvariant();
        CustomerName = customerName.Trim();
        DateFrom = dateFrom;
        DateTo = dateTo;
        CurrencyCode = currencyCode.Trim().ToUpperInvariant();
        OpeningBalanceAmount = openingBalanceAmount;
        TotalChargeAmount = totalChargeAmount;
        TotalSettlementAmount = totalSettlementAmount;
        ClosingBalanceAmount = closingBalanceAmount;
        OpenInvoiceAmount = openInvoiceAmount;
        OverdueInvoiceAmount = overdueInvoiceAmount;
        GeneratedAtUtc = DateTime.UtcNow;
        GeneratedByUserId = actorUserId;
        CreatedByUserId = actorUserId;
    }

    private CustomerStatement()
    {
    }

    public void AddLine(
        string entryType,
        string documentType,
        long documentId,
        string documentNumber,
        DateOnly businessDate,
        DateOnly? dueDate,
        string currencyCode,
        decimal chargeAmount,
        decimal settlementAmount,
        decimal runningBalanceAmount,
        string statusCode,
        string? referenceText)
    {
        Lines.Add(new CustomerStatementLine(
            customerStatementId: Id,
            entryType: entryType,
            documentType: documentType,
            documentId: documentId,
            documentNumber: documentNumber,
            businessDate: businessDate,
            dueDate: dueDate,
            currencyCode: currencyCode,
            chargeAmount: chargeAmount,
            settlementAmount: settlementAmount,
            runningBalanceAmount: runningBalanceAmount,
            statusCode: statusCode,
            referenceText: referenceText));
    }

    public CustomerStatementDeliveryLog AddDeliveryLog(
        DateTime deliveredAtUtc,
        string channelCode,
        string? recipientAddress,
        string deliveryStatusCode,
        string? notes,
        long actorUserId)
    {
        var log = new CustomerStatementDeliveryLog(
            customerStatementId: Id,
            deliveredAtUtc: deliveredAtUtc,
            channelCode: channelCode,
            recipientAddress: recipientAddress,
            deliveryStatusCode: deliveryStatusCode,
            notes: notes,
            actorUserId: actorUserId);

        DeliveryLogs.Add(log);
        Touch(actorUserId);
        return log;
    }
}
