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

namespace CashLineERP.Api.Tests;

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

        var requisitionService = new PurchaseRequisitionService(new PurchaseRequisitionRepository(dbContext));
        var rfqService = new RfqService(new RfqRepository(dbContext));
        var quotationService = new SupplierQuotationService(new SupplierQuotationRepository(dbContext));
        var evaluationService = new RfqEvaluationService(new RfqEvaluationRepository(dbContext));
        var purchaseOrderService = new PurchaseOrderService(new PurchaseOrderRepository(dbContext));
        var goodsReceiptService = new GoodsReceiptService(new GoodsReceiptRepository(dbContext));
        var inventoryMovementService = new InventoryMovementService(new InventoryMovementReadRepository(dbContext));

        var requisition = requisitionService.CreatePurchaseRequisition(new CreatePurchaseRequisitionRequest(
            BranchId: 1,
            RequesterUserId: 1,
            RequisitionDate: new DateOnly(2026, 4, 12),
            NeedByDate: new DateOnly(2026, 4, 18),
            PurposeText: "Warehouse replenishment",
            CurrencyCode: "EGP",
            Lines:
            [
                new PurchaseRequisitionLineRequest(1001, 12m, "EA", new DateOnly(2026, 4, 18), 24m, "Carton stock")
            ]));

        var submittedRequisition = requisitionService.SubmitPurchaseRequisition(requisition.PurchaseRequisitionId, 1);
        Assert.Equal("SUBMITTED", submittedRequisition.StatusCode);
        Assert.NotNull(requisitionService.GetPurchaseRequisition(submittedRequisition.PurchaseRequisitionId));
        Assert.Contains(requisitionService.GetPurchaseRequisitions(), item => item.PurchaseRequisitionId == submittedRequisition.PurchaseRequisitionId);

        var rfq = rfqService.CreateRfq(new CreateRfqRequest(
            BranchId: 1,
            DepartmentId: 3,
            PurchaseRequisitionId: submittedRequisition.PurchaseRequisitionId,
            RequesterUserId: 1,
            BuyerUserId: 2,
            RfqType: "STANDARD",
            RfqDate: new DateOnly(2026, 4, 13),
            ValidUntilDate: new DateOnly(2026, 4, 15),
            CurrencyCode: "EGP",
            Notes: "Two-day sourcing cycle",
            Lines:
            [
                new CreateRfqLineRequest(
                    submittedRequisition.Lines.First().LineId,
                    submittedRequisition.Lines.First().ItemId,
                    "Warehouse replenishment item",
                    submittedRequisition.Lines.First().RequestedQuantity,
                    submittedRequisition.Lines.First().UnitOfMeasureCode,
                    submittedRequisition.Lines.First().NeedByDate,
                    "VAT14",
                    24m,
                    "Urgent sourcing")
            ],
            SupplierIds: [1]));

        var submittedRfq = rfqService.SubmitRfq(rfq.RfqId, 2);
        Assert.Equal("SUBMITTED", submittedRfq.StatusCode);
        Assert.Single(submittedRfq.Suppliers);
        Assert.NotNull(rfqService.GetRfq(submittedRfq.RfqId));
        Assert.Contains(rfqService.GetRfqs(), item => item.RfqId == submittedRfq.RfqId);

        var quotation = quotationService.CreateQuotation(new CreateSupplierQuotationRequest(
            RequestForQuotationId: submittedRfq.RfqId,
            SupplierId: 1,
            QuoteDate: new DateOnly(2026, 4, 14),
            ValidUntilDate: new DateOnly(2026, 4, 20),
            CurrencyCode: "EGP",
            FxRate: 1m,
            Notes: "Awardable supplier quote",
            Lines:
            [
                new CreateSupplierQuotationLineRequest(
                    submittedRfq.Lines.First().RfqLineId,
                    submittedRfq.Lines.First().ItemId,
                    submittedRfq.Lines.First().RequestedQuantity,
                    submittedRfq.Lines.First().UnitOfMeasureCode,
                    23.5m,
                    0m,
                    0m,
                    "VAT14",
                    2)
            ]));

        var submittedQuotation = quotationService.SubmitQuotation(quotation.SupplierQuotationId, 3);
        Assert.Equal("SUBMITTED", submittedQuotation.StatusCode);
        Assert.NotNull(quotationService.GetQuotation(submittedQuotation.SupplierQuotationId));
        Assert.Contains(quotationService.GetQuotations(submittedRfq.RfqId), item => item.SupplierQuotationId == submittedQuotation.SupplierQuotationId);

        var evaluation = evaluationService.CreateEvaluation(new CreateRfqEvaluationRequest(
            RequestForQuotationId: submittedRfq.RfqId,
            EvaluationDate: new DateOnly(2026, 4, 15),
            Notes: "Commercially preferred supplier",
            Lines:
            [
                new CreateRfqEvaluationLineRequest(
                    submittedQuotation.SupplierQuotationId,
                    submittedQuotation.SupplierId,
                    96m,
                    93m,
                    90m,
                    95m,
                    1,
                    true,
                    "Best overall score")
            ]));

        Assert.Equal(submittedQuotation.SupplierQuotationId, evaluation.ApprovedQuotationId);
        Assert.Equal(1, evaluation.RecommendedSupplierId);

        var finalizedEvaluation = evaluationService.FinalizeRecommendation(evaluation.RfqEvaluationId, 4);
        Assert.Equal("APPROVED", finalizedEvaluation.StatusCode);
        Assert.Equal(1, finalizedEvaluation.RecommendedSupplierId);
        Assert.Equal(submittedQuotation.SupplierQuotationId, finalizedEvaluation.ApprovedQuotationId);
        Assert.NotNull(evaluationService.GetEvaluation(finalizedEvaluation.RfqEvaluationId));
        Assert.Contains(evaluationService.GetEvaluations(submittedRfq.RfqId), item => item.RfqEvaluationId == finalizedEvaluation.RfqEvaluationId);

        var purchaseOrder = evaluationService.AwardPurchaseOrder(
            finalizedEvaluation.RfqEvaluationId,
            new AwardPurchaseOrderRequest(ActorUserId: 4, PurchaseOrderDate: new DateOnly(2026, 4, 16), DefaultWarehouseId: 1));

        Assert.Equal("APPROVED", purchaseOrder.StatusCode);
        Assert.Single(purchaseOrder.Lines);
        Assert.Equal(0m, purchaseOrder.Lines.First().ReceivedQuantity);
        Assert.NotNull(purchaseOrderService.GetPurchaseOrder(purchaseOrder.PurchaseOrderId));
        Assert.Contains(purchaseOrderService.GetPurchaseOrders(), item => item.PurchaseOrderId == purchaseOrder.PurchaseOrderId);

        var goodsReceipt = goodsReceiptService.CreateGoodsReceipt(new CreateGoodsReceiptRequest(
            BranchId: purchaseOrder.BranchId,
            PurchaseOrderId: purchaseOrder.PurchaseOrderId,
            SupplierId: purchaseOrder.SupplierId,
            ReceivedAtUtc: new DateTime(2026, 4, 17, 10, 0, 0, DateTimeKind.Utc),
            ReceivedByUserId: 1,
            InspectionStatus: "ACCEPTED",
            Lines:
            [
                new CreateGoodsReceiptLineRequest(
                    purchaseOrder.Lines.First().PurchaseOrderLineId,
                    purchaseOrder.Lines.First().ItemId,
                    1,
                    null,
                    12m,
                    12m,
                    0m,
                    "BATCH-RCV-001",
                    null)
            ]));

        var postedGoodsReceipt = goodsReceiptService.PostGoodsReceipt(goodsReceipt.GoodsReceiptId, 4);
        Assert.Equal("POSTED", postedGoodsReceipt.StatusCode);
        Assert.NotNull(goodsReceiptService.GetGoodsReceipt(postedGoodsReceipt.GoodsReceiptId));
        Assert.Contains(goodsReceiptService.GetGoodsReceipts(), item => item.GoodsReceiptId == postedGoodsReceipt.GoodsReceiptId);

        var refreshedPurchaseOrder = purchaseOrderService.GetPurchaseOrder(purchaseOrder.PurchaseOrderId);
        Assert.NotNull(refreshedPurchaseOrder);
        Assert.Equal(12m, refreshedPurchaseOrder!.Lines.First().ReceivedQuantity);

        var movements = inventoryMovementService.GetInventoryMovements();
        Assert.Contains(movements, movement =>
            movement.MovementType == "GRN_RECEIPT" &&
            movement.ItemId == purchaseOrder.Lines.First().ItemId &&
            movement.QuantityMoved == 12m &&
            movement.StatusCode == "POSTED");
    }
}
