using CashLineERP.Domain.Common;
using CashLineERP.SharedKernel.Workflow;

namespace CashLineERP.Domain.Sales;

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

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

    public long BranchId { get; private set; }

    public long CustomerId { get; private set; }

    public DateOnly OrderDate { get; private set; }

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

    public decimal ExchangeRate { get; private set; } = 1m;

    public long StatusId { get; private set; } = WorkflowStatusSeedIds.Draft;

    public decimal SubtotalAmount { get; private set; }

    public decimal TaxAmount { get; private set; }

    public decimal TotalAmount { get; private set; }

    public string CreditStatusCode { get; private set; } = "NOT_CHECKED";

    public string? CreditHoldReason { get; private set; }

    public decimal? CreditLimitAmountAtCheck { get; private set; }

    public decimal? CreditExposureAmountAtCheck { get; private set; }

    public int OverdueInvoiceCountAtCheck { get; private set; }

    public DateTime? CreditCheckedAtUtc { get; private set; }

    public string? CreditReleaseDecisionCode { get; private set; }

    public string? CreditReleaseNotes { get; private set; }

    public DateTime? CreditReleasedAtUtc { get; private set; }

    public long? CreditReleasedByUserId { get; private set; }

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

    public SalesOrder(
        string salesOrderNumber,
        long branchId,
        long customerId,
        DateOnly orderDate,
        string currencyCode,
        decimal exchangeRate)
    {
        SalesOrderNumber = salesOrderNumber;
        BranchId = branchId;
        CustomerId = customerId;
        OrderDate = orderDate;
        CurrencyCode = currencyCode;
        ExchangeRate = exchangeRate;
    }

    private SalesOrder()
    {
    }

    public void AddLine(
        int lineNumber,
        long itemId,
        long warehouseId,
        decimal orderedQuantity,
        string unitOfMeasureCode,
        decimal unitPrice,
        decimal baseUnitPrice,
        long? pricingRuleId,
        string? pricingRuleCode,
        string pricingSourceCode,
        string? taxCode)
    {
        Lines.Add(new SalesOrderLine(
            salesOrderId: Id,
            lineNumber: lineNumber,
            itemId: itemId,
            warehouseId: warehouseId,
            orderedQuantity: orderedQuantity,
            unitOfMeasureCode: unitOfMeasureCode,
            unitPrice: unitPrice,
            baseUnitPrice: baseUnitPrice,
            pricingRuleId: pricingRuleId,
            pricingRuleCode: pricingRuleCode,
            pricingSourceCode: pricingSourceCode,
            taxCode: taxCode));

        RecalculateTotals();
    }

    public void ApplyApprovedDiscountOverride(long salesOrderLineId, long discountOverrideId, decimal approvedUnitPrice)
    {
        if (StatusId != WorkflowStatusSeedIds.Draft)
        {
            throw new InvalidOperationException("Discount overrides can only be applied while the sales order is draft.");
        }

        var line = Lines.SingleOrDefault(entry => entry.Id == salesOrderLineId)
            ?? throw new InvalidOperationException("Sales order line was not found for discount override.");
        line.ApplyApprovedDiscountOverride(discountOverrideId, approvedUnitPrice);
        RecalculateTotals();
    }

    public void Submit(long actorUserId, decimal? creditLimitAmountAtCheck, decimal creditExposureAmountAtCheck, int overdueInvoiceCountAtCheck)
    {
        StatusId = WorkflowStatusSeedIds.Submitted;
        CreditStatusCode = "CLEAR";
        CreditHoldReason = null;
        CreditLimitAmountAtCheck = creditLimitAmountAtCheck;
        CreditExposureAmountAtCheck = creditExposureAmountAtCheck;
        OverdueInvoiceCountAtCheck = overdueInvoiceCountAtCheck;
        CreditCheckedAtUtc = DateTime.UtcNow;
        CreditReleaseDecisionCode = null;
        CreditReleaseNotes = null;
        CreditReleasedAtUtc = null;
        CreditReleasedByUserId = null;
        Touch(actorUserId);
    }

    public void SubmitOnCreditHold(long actorUserId, string creditHoldReason, decimal? creditLimitAmountAtCheck, decimal creditExposureAmountAtCheck, int overdueInvoiceCountAtCheck)
    {
        StatusId = WorkflowStatusSeedIds.Submitted;
        CreditStatusCode = "ON_HOLD";
        CreditHoldReason = creditHoldReason;
        CreditLimitAmountAtCheck = creditLimitAmountAtCheck;
        CreditExposureAmountAtCheck = creditExposureAmountAtCheck;
        OverdueInvoiceCountAtCheck = overdueInvoiceCountAtCheck;
        CreditCheckedAtUtc = DateTime.UtcNow;
        CreditReleaseDecisionCode = null;
        CreditReleaseNotes = null;
        CreditReleasedAtUtc = null;
        CreditReleasedByUserId = null;
        Touch(actorUserId);
    }

    public void ReleaseCreditHold(long actorUserId, string notes)
    {
        if (!string.Equals(CreditStatusCode, "ON_HOLD", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Sales order is not on credit hold.");
        }

        CreditStatusCode = "RELEASED";
        CreditReleaseDecisionCode = "RELEASED";
        CreditReleaseNotes = notes;
        CreditReleasedAtUtc = DateTime.UtcNow;
        CreditReleasedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void RejectCreditRelease(long actorUserId, string notes)
    {
        if (!string.Equals(CreditStatusCode, "ON_HOLD", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Sales order is not on credit hold.");
        }

        CreditStatusCode = "REJECTED";
        CreditReleaseDecisionCode = "REJECTED";
        CreditReleaseNotes = notes;
        CreditReleasedAtUtc = DateTime.UtcNow;
        CreditReleasedByUserId = actorUserId;
        StatusId = WorkflowStatusSeedIds.Rejected;
        Touch(actorUserId);
    }

    private void RecalculateTotals()
    {
        SubtotalAmount = Lines.Sum(line => line.LineNetAmount);
        TaxAmount = Lines.Sum(line => line.LineTaxAmount);
        TotalAmount = Lines.Sum(line => line.LineTotalAmount);
    }
}
