using CashLineERP.Application.Finance;
using CashLineERP.Application.Inventory;
using CashLineERP.Application.Sales;
using CashLineERP.Contracts.Finance;
using CashLineERP.Contracts.Sales;
using CashLineERP.Infrastructure.Persistence.Repositories;

namespace CashLineERP.Api.Tests;

public sealed class SalesReturnAndCreditNoteServiceQaTests
{
    [Fact]
    public void Posted_Sales_Return_Can_Issue_And_Post_A_Credit_Note_With_Reversing_Gl_Traceability()
    {
        using var dbContext = QaDbContextFactory.CreateContext();

        var salesOrderService = new SalesOrderService(new SalesOrderRepository(dbContext));
        var dispatchService = new DispatchOrderService(new DispatchOrderRepository(dbContext));
        var salesReturnService = new SalesReturnService(new SalesReturnRepository(dbContext));
        var invoiceService = new AccountsReceivableInvoiceService(new AccountsReceivableInvoiceRepository(dbContext));
        var inventoryMovementService = new InventoryMovementService(new InventoryMovementReadRepository(dbContext));
        var generalLedgerService = new GeneralLedgerService(new GeneralLedgerReadRepository(dbContext));

        var salesOrder = salesOrderService.CreateSalesOrder(new CreateSalesOrderRequest(
            BranchId: 1,
            CustomerId: 2,
            OrderDate: new DateOnly(2026, 4, 29),
            CurrencyCode: "EGP",
            ExchangeRate: 1m,
            Lines:
            [
                new CreateSalesOrderLineRequest(1001, 1, 4m, "EA", 32m, "VAT14")
            ]));

        var submittedSalesOrder = salesOrderService.SubmitSalesOrder(salesOrder.SalesOrderId, 2);
        Assert.Equal("CLEAR", submittedSalesOrder.CreditStatusCode);

        var dispatch = dispatchService.CreateDispatchOrder(new CreateDispatchOrderRequest(
            BranchId: 1,
            SalesOrderId: submittedSalesOrder.SalesOrderId,
            DispatchWarehouseId: 1,
            PlannedShipAtUtc: new DateTime(2026, 4, 29, 10, 0, 0, DateTimeKind.Utc),
            Lines:
            [
                new CreateDispatchOrderLineRequest(submittedSalesOrder.Lines.First().SalesOrderLineId, 1001, 4m, null, "BATCH-RET-001", null)
            ]));

        var postedDispatch = dispatchService.PostDispatchOrder(dispatch.DispatchOrderId, new PostDispatchOrderRequest(
            ActorUserId: 6,
            ShippedAtUtc: new DateTime(2026, 4, 29, 12, 0, 0, DateTimeKind.Utc),
            DeliveredAtUtc: new DateTime(2026, 4, 29, 15, 0, 0, DateTimeKind.Utc),
            ProofOfDeliveryReceiver: "Customer Return Receiver"));

        var salesReturn = salesReturnService.CreateSalesReturn(new CreateSalesReturnRequest(
            BranchId: 1,
            CustomerId: 2,
            SalesOrderId: submittedSalesOrder.SalesOrderId,
            DispatchOrderId: postedDispatch.DispatchOrderId,
            ReturnDate: new DateOnly(2026, 4, 30),
            ReturnWarehouseId: 1,
            ReasonCode: "CUSTOMER_RETURN",
            Notes: "Customer returned part of the shipment.",
            Lines:
            [
                new CreateSalesReturnLineRequest(
                    postedDispatch.Lines.First().DispatchOrderLineId,
                    submittedSalesOrder.Lines.First().SalesOrderLineId,
                    1001,
                    2m,
                    null,
                    "BATCH-RET-001",
                    null,
                    "2 units returned")
            ]));

        var submittedReturn = salesReturnService.SubmitSalesReturn(salesReturn.SalesReturnId, 3);
        Assert.Equal("SUBMITTED", submittedReturn.StatusCode);

        var postedReturn = salesReturnService.PostSalesReturn(submittedReturn.SalesReturnId, 4);
        Assert.Equal("POSTED", postedReturn.StatusCode);
        Assert.Equal(2m, postedReturn.TotalReturnedQuantity);

        var returnMovement = inventoryMovementService.GetInventoryMovements()
            .Single(movement => movement.MovementType == "SALES_RETURN_RECEIPT" && movement.QuantityMoved == 2m);
        Assert.Equal(1, returnMovement.ToWarehouseId);

        var returnWithCreditNote = salesReturnService.IssueCreditNote(
            postedReturn.SalesReturnId,
            new IssueSalesReturnCreditNoteRequest(
                ActorUserId: 4,
                InvoiceDate: new DateOnly(2026, 4, 30),
                TaxDate: new DateOnly(2026, 4, 30),
                DueDate: new DateOnly(2026, 4, 30)));

        Assert.NotNull(returnWithCreditNote.CreditNoteInvoiceId);

        var creditNote = invoiceService.GetInvoice(returnWithCreditNote.CreditNoteInvoiceId!.Value);
        Assert.NotNull(creditNote);
        Assert.Equal("CREDIT_NOTE", creditNote!.InvoiceType);
        Assert.Equal(2m, creditNote.Lines.Single().QuantityInvoiced);

        var postedCreditNote = AccountsReceivableWorkflowTestHelper.SubmitApproveAndPostInvoice(
            invoiceService,
            creditNote.AccountsReceivableInvoiceId);

        Assert.Equal("POSTED", postedCreditNote.StatusCode);
        Assert.NotNull(postedCreditNote.GeneralLedgerBatchId);

        var creditBatch = generalLedgerService.GetGeneralLedgerBatches()
            .Single(batch => batch.SourceDocumentId == postedCreditNote.AccountsReceivableInvoiceId);
        var creditJournal = Assert.Single(creditBatch.Journals);
        Assert.Equal(2, creditJournal.Lines.Count);
        Assert.Contains(creditJournal.Lines, line => line.GeneralLedgerAccountId == 4001L && line.DebitAmount == postedCreditNote.TotalAmount && line.CreditAmount == 0m);
        Assert.Contains(creditJournal.Lines, line => line.GeneralLedgerAccountId == 1101L && line.CreditAmount == postedCreditNote.TotalAmount && line.DebitAmount == 0m);
    }
}
