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

namespace CashLineERP.Api.Tests;

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

        var procurement = BuildProcurementServices(dbContext);
        var sales = BuildSalesAndFinanceServices(dbContext);
        var inventoryMovementService = new InventoryMovementService(new InventoryMovementReadRepository(dbContext));
        var generalLedgerService = new GeneralLedgerService(new GeneralLedgerReadRepository(dbContext));

        var purchaseOrder = CreateAwardedPurchaseOrder(procurement);

        var postedGoodsReceipt = procurement.GoodsReceiptService.PostGoodsReceipt(
            procurement.GoodsReceiptService.CreateGoodsReceipt(new CreateGoodsReceiptRequest(
                BranchId: purchaseOrder.BranchId,
                PurchaseOrderId: purchaseOrder.PurchaseOrderId,
                SupplierId: purchaseOrder.SupplierId,
                ReceivedAtUtc: new DateTime(2026, 4, 18, 9, 0, 0, DateTimeKind.Utc),
                ReceivedByUserId: 1,
                InspectionStatus: "ACCEPTED",
                Lines:
                [
                    new CreateGoodsReceiptLineRequest(
                        purchaseOrder.Lines.First().PurchaseOrderLineId,
                        purchaseOrder.Lines.First().ItemId,
                        1,
                        null,
                        6m,
                        6m,
                        0m,
                        "BATCH-GRN-TRACE-1",
                        null)
                ])).GoodsReceiptId,
            4);

        var createdBill = procurement.AccountsPayableBillService.CreateBill(new CreateAccountsPayableBillRequest(
            SupplierId: purchaseOrder.SupplierId,
            PurchaseOrderId: purchaseOrder.PurchaseOrderId,
            GoodsReceiptId: postedGoodsReceipt.GoodsReceiptId,
            BillDate: new DateOnly(2026, 4, 18),
            DueDate: new DateOnly(2026, 4, 25),
            CurrencyCode: "EGP",
            Lines:
            [
                new CreateAccountsPayableBillLineRequest(
                    purchaseOrder.Lines.First().PurchaseOrderLineId,
                    purchaseOrder.Lines.First().ItemId,
                    6m,
                    purchaseOrder.Lines.First().UnitOfMeasureCode,
                    purchaseOrder.Lines.First().UnitPrice,
                    purchaseOrder.Lines.First().TaxCode)
            ]));

        var submittedBill = procurement.AccountsPayableBillService.SubmitBill(createdBill.AccountsPayableBillId, 3);
        var match = procurement.AccountsPayableBillService.GetBillMatch(submittedBill.AccountsPayableBillId);
        Assert.Equal(0, match.BlockingIssueCount);
        procurement.AccountsPayableBillService.ApproveMatch(submittedBill.AccountsPayableBillId, 4);
        var postedBill = procurement.AccountsPayableBillService.PostBill(submittedBill.AccountsPayableBillId, 4);

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

        var submittedSalesOrder = sales.SalesOrderService.SubmitSalesOrder(salesOrder.SalesOrderId, 2);
        var postedDispatch = sales.DispatchOrderService.PostDispatchOrder(
            sales.DispatchOrderService.CreateDispatchOrder(new CreateDispatchOrderRequest(
                BranchId: 1,
                SalesOrderId: submittedSalesOrder.SalesOrderId,
                DispatchWarehouseId: 1,
                PlannedShipAtUtc: new DateTime(2026, 4, 18, 12, 0, 0, DateTimeKind.Utc),
                Lines:
                [
                    new CreateDispatchOrderLineRequest(
                        submittedSalesOrder.Lines.First().SalesOrderLineId,
                        submittedSalesOrder.Lines.First().ItemId,
                        3m,
                        null,
                        "BATCH-DSP-TRACE-1",
                        null)
                ])).DispatchOrderId,
            new PostDispatchOrderRequest(
                ActorUserId: 6,
                ShippedAtUtc: new DateTime(2026, 4, 18, 13, 0, 0, DateTimeKind.Utc),
                DeliveredAtUtc: new DateTime(2026, 4, 18, 16, 0, 0, DateTimeKind.Utc),
                ProofOfDeliveryReceiver: "Warehouse Trace Receiver"));

        var postedInvoice = AccountsReceivableWorkflowTestHelper.SubmitApproveAndPostInvoice(
            sales.InvoiceService,
            sales.InvoiceService.CreateInvoice(new CreateAccountsReceivableInvoiceRequest(
                CustomerId: 2,
                SalesOrderId: submittedSalesOrder.SalesOrderId,
                DispatchOrderId: postedDispatch.DispatchOrderId,
                InvoiceType: "TAX",
                InvoiceDate: new DateOnly(2026, 4, 18),
                TaxDate: new DateOnly(2026, 4, 18),
                DueDate: new DateOnly(2026, 4, 25),
                CurrencyCode: "EGP",
                Lines:
                [
                    new CreateAccountsReceivableInvoiceLineRequest(
                        submittedSalesOrder.Lines.First().SalesOrderLineId,
                        submittedSalesOrder.Lines.First().ItemId,
                        3m,
                        submittedSalesOrder.Lines.First().UnitOfMeasureCode,
                        submittedSalesOrder.Lines.First().UnitPrice,
                        submittedSalesOrder.Lines.First().TaxCode)
                ])).AccountsReceivableInvoiceId);

        var postedReceipt = AccountsReceivableWorkflowTestHelper.SubmitApproveAndPostReceipt(
            sales.ReceiptService,
            sales.ReceiptService.CreateReceipt(new CreateAccountsReceivableReceiptRequest(
                CustomerId: 2,
                ReceiptDate: new DateOnly(2026, 4, 19),
                ReceiptMethod: "BANK_TRANSFER",
                CurrencyCode: "EGP",
                AmountAmount: postedInvoice.TotalAmount,
                ReferenceNumber: "BANK-TRACE-0001",
                Notes: "Traceability receipt",
                Allocations:
                [
                    new CreateAccountsReceivableReceiptAllocationRequest(
                        postedInvoice.AccountsReceivableInvoiceId,
                        postedInvoice.TotalAmount,
                        new DateOnly(2026, 4, 19))
                ])).AccountsReceivableReceiptId);

        var inventoryMovements = inventoryMovementService.GetInventoryMovements();
        Assert.Contains(inventoryMovements, movement => movement.MovementType == "GRN_RECEIPT" && movement.QuantityMoved == 6m);
        Assert.Contains(inventoryMovements, movement => movement.MovementType == "DISPATCH_ISSUE" && movement.QuantityMoved == 3m);

        var ledgerBatches = generalLedgerService.GetGeneralLedgerBatches();
        Assert.Contains(ledgerBatches, batch => batch.BatchType == "AP_BILL" && batch.SourceDocumentId == postedBill.AccountsPayableBillId);
        Assert.Contains(ledgerBatches, batch => batch.BatchType == "AR_INVOICE" && batch.SourceDocumentId == postedInvoice.AccountsReceivableInvoiceId);
        Assert.Contains(ledgerBatches, batch => batch.BatchType == "AR_RECEIPT" && batch.SourceDocumentId == postedReceipt.AccountsReceivableReceiptId);
        Assert.All(
            ledgerBatches.Where(batch => batch.BatchType is "AP_BILL" or "AR_INVOICE" or "AR_RECEIPT"),
            batch =>
            {
                Assert.NotEmpty(batch.Journals);
                Assert.All(batch.Journals, journal => Assert.NotEmpty(journal.Lines));
            });

        Assert.Contains(sales.InvoiceService.GetInvoices(), invoice => invoice.AccountsReceivableInvoiceId == postedInvoice.AccountsReceivableInvoiceId && invoice.StatusCode == "POSTED");
        Assert.Contains(sales.ReceiptService.GetReceipts(), receipt => receipt.AccountsReceivableReceiptId == postedReceipt.AccountsReceivableReceiptId && receipt.StatusCode == "POSTED");
        Assert.Contains(sales.SalesOrderService.GetSalesOrders(), order => order.SalesOrderId == submittedSalesOrder.SalesOrderId && order.StatusCode == "SUBMITTED");
        Assert.Contains(sales.DispatchOrderService.GetDispatchOrders(), dispatch => dispatch.DispatchOrderId == postedDispatch.DispatchOrderId && dispatch.StatusCode == "POSTED");
    }

    private static PurchaseOrderResponse CreateAwardedPurchaseOrder(ProcurementServices procurement)
    {
        var requisition = procurement.PurchaseRequisitionService.CreatePurchaseRequisition(new CreatePurchaseRequisitionRequest(
            BranchId: 1,
            RequesterUserId: 1,
            RequisitionDate: new DateOnly(2026, 4, 17),
            NeedByDate: new DateOnly(2026, 4, 19),
            PurposeText: "Traceability stock replenishment",
            CurrencyCode: "EGP",
            Lines:
            [
                new PurchaseRequisitionLineRequest(1001, 6m, "EA", new DateOnly(2026, 4, 19), 24m, "Traceability stock")
            ]));

        var submittedRequisition = procurement.PurchaseRequisitionService.SubmitPurchaseRequisition(requisition.PurchaseRequisitionId, 1);
        var rfq = procurement.RfqService.CreateRfq(new CreateRfqRequest(
            BranchId: 1,
            DepartmentId: 3,
            PurchaseRequisitionId: submittedRequisition.PurchaseRequisitionId,
            RequesterUserId: 1,
            BuyerUserId: 2,
            RfqType: "STANDARD",
            RfqDate: new DateOnly(2026, 4, 17),
            ValidUntilDate: new DateOnly(2026, 4, 18),
            CurrencyCode: "EGP",
            Notes: "Traceability RFQ",
            Lines:
            [
                new CreateRfqLineRequest(
                    submittedRequisition.Lines.First().LineId,
                    submittedRequisition.Lines.First().ItemId,
                    "Traceability line",
                    6m,
                    "EA",
                    new DateOnly(2026, 4, 19),
                    "VAT14",
                    24m,
                    null)
            ],
            SupplierIds: [1]));

        var submittedRfq = procurement.RfqService.SubmitRfq(rfq.RfqId, 2);
        var quotation = procurement.SupplierQuotationService.CreateQuotation(new CreateSupplierQuotationRequest(
            RequestForQuotationId: submittedRfq.RfqId,
            SupplierId: 1,
            QuoteDate: new DateOnly(2026, 4, 17),
            ValidUntilDate: new DateOnly(2026, 4, 20),
            CurrencyCode: "EGP",
            FxRate: 1m,
            Notes: "Traceability quotation",
            Lines:
            [
                new CreateSupplierQuotationLineRequest(submittedRfq.Lines.First().RfqLineId, 1001, 6m, "EA", 24m, 0m, 0m, "VAT14", 1)
            ]));

        var submittedQuotation = procurement.SupplierQuotationService.SubmitQuotation(quotation.SupplierQuotationId, 3);
        var evaluation = procurement.RfqEvaluationService.CreateEvaluation(new CreateRfqEvaluationRequest(
            RequestForQuotationId: submittedRfq.RfqId,
            EvaluationDate: new DateOnly(2026, 4, 18),
            Notes: "Traceability evaluation",
            Lines:
            [
                new CreateRfqEvaluationLineRequest(submittedQuotation.SupplierQuotationId, 1, 95m, 92m, 94m, 93m, 1, true, "Recommended")
            ]));

        var finalized = procurement.RfqEvaluationService.FinalizeRecommendation(evaluation.RfqEvaluationId, 4);
        return procurement.RfqEvaluationService.AwardPurchaseOrder(
            finalized.RfqEvaluationId,
            new AwardPurchaseOrderRequest(ActorUserId: 4, PurchaseOrderDate: new DateOnly(2026, 4, 18), DefaultWarehouseId: 1));
    }

    private static ProcurementServices BuildProcurementServices(CashLineErpDbContext dbContext) =>
        new(
            new PurchaseRequisitionService(new PurchaseRequisitionRepository(dbContext)),
            new RfqService(new RfqRepository(dbContext)),
            new SupplierQuotationService(new SupplierQuotationRepository(dbContext)),
            new RfqEvaluationService(new RfqEvaluationRepository(dbContext)),
            new GoodsReceiptService(new GoodsReceiptRepository(dbContext)),
            new AccountsPayableBillService(new AccountsPayableBillRepository(dbContext)));

    private static SalesAndFinanceServices BuildSalesAndFinanceServices(CashLineErpDbContext dbContext) =>
        new(
            new SalesOrderService(new SalesOrderRepository(dbContext)),
            new DispatchOrderService(new DispatchOrderRepository(dbContext)),
            new AccountsReceivableInvoiceService(new AccountsReceivableInvoiceRepository(dbContext)),
            new AccountsReceivableReceiptService(new AccountsReceivableReceiptRepository(dbContext)));

    private sealed record ProcurementServices(
        PurchaseRequisitionService PurchaseRequisitionService,
        RfqService RfqService,
        SupplierQuotationService SupplierQuotationService,
        RfqEvaluationService RfqEvaluationService,
        GoodsReceiptService GoodsReceiptService,
        AccountsPayableBillService AccountsPayableBillService);

    private sealed record SalesAndFinanceServices(
        SalesOrderService SalesOrderService,
        DispatchOrderService DispatchOrderService,
        AccountsReceivableInvoiceService InvoiceService,
        AccountsReceivableReceiptService ReceiptService);
}
