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

namespace CashLineERP.Api.Tests;

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

        var salesOrderService = new SalesOrderService(new SalesOrderRepository(dbContext));
        var dispatchService = new DispatchOrderService(new DispatchOrderRepository(dbContext));

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

        var submittedOrder = salesOrderService.SubmitSalesOrder(salesOrder.SalesOrderId, 2);
        Assert.Equal("SUBMITTED", submittedOrder.StatusCode);
        Assert.Equal("ON_HOLD", submittedOrder.CreditStatusCode);
        Assert.Contains("credit limit", submittedOrder.CreditHoldReason, StringComparison.OrdinalIgnoreCase);

        Assert.Throws<InvalidOperationException>(() =>
            dispatchService.CreateDispatchOrder(new CreateDispatchOrderRequest(
                BranchId: 1,
                SalesOrderId: submittedOrder.SalesOrderId,
                DispatchWarehouseId: 1,
                PlannedShipAtUtc: new DateTime(2026, 4, 27, 10, 0, 0, DateTimeKind.Utc),
                Lines:
                [
                    new CreateDispatchOrderLineRequest(submittedOrder.Lines.First().SalesOrderLineId, 1001, 50m, null, "BATCH-CREDIT-HOLD", null)
                ])));

        var releasedOrder = salesOrderService.ReleaseCreditHold(
            submittedOrder.SalesOrderId,
            new ReleaseSalesOrderCreditHoldRequest(
                ActorUserId: 4,
                DecisionCode: "RELEASE",
                Notes: "Finance checker approved temporary credit release."));

        Assert.Equal("RELEASED", releasedOrder.CreditStatusCode);
        Assert.Equal("RELEASED", releasedOrder.CreditReleaseDecisionCode);
        Assert.Equal(4, releasedOrder.CreditReleasedByUserId);

        var dispatch = dispatchService.CreateDispatchOrder(new CreateDispatchOrderRequest(
            BranchId: 1,
            SalesOrderId: releasedOrder.SalesOrderId,
            DispatchWarehouseId: 1,
            PlannedShipAtUtc: new DateTime(2026, 4, 27, 12, 0, 0, DateTimeKind.Utc),
            Lines:
            [
                new CreateDispatchOrderLineRequest(releasedOrder.Lines.First().SalesOrderLineId, 1001, 50m, null, "BATCH-CREDIT-REL", null)
            ]));

        Assert.True(dispatch.DispatchOrderId > 0);
    }

    [Fact]
    public void Customer_Credit_Control_Reports_Overdue_Receivables_As_Hold_Risk()
    {
        using var dbContext = QaDbContextFactory.CreateContext();

        var salesOrderService = new SalesOrderService(new SalesOrderRepository(dbContext));
        var invoiceService = new AccountsReceivableInvoiceService(new AccountsReceivableInvoiceRepository(dbContext));

        var invoice = invoiceService.CreateInvoice(new CreateAccountsReceivableInvoiceRequest(
            CustomerId: 2,
            SalesOrderId: 1,
            DispatchOrderId: 1,
            InvoiceType: "TAX",
            InvoiceDate: new DateOnly(2026, 4, 1),
            TaxDate: new DateOnly(2026, 4, 1),
            DueDate: new DateOnly(2026, 4, 10),
            CurrencyCode: "EGP",
            Lines:
            [
                new CreateAccountsReceivableInvoiceLineRequest(1, 1001, 2m, "EA", 32m, "VAT14")
            ]));

        AccountsReceivableWorkflowTestHelper.SubmitApproveAndPostInvoice(invoiceService, invoice.AccountsReceivableInvoiceId);

        var creditControl = salesOrderService.GetCustomerCreditControl(2, "EGP", new DateOnly(2026, 4, 20));

        Assert.Equal(2, creditControl.CustomerId);
        Assert.True(creditControl.ShouldHoldNewOrders);
        Assert.True(creditControl.OverdueInvoiceCount > 0);
        Assert.Contains("overdue", creditControl.HoldReason, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Customer_Account_Credit_Hold_Forces_Sales_Order_Hold_And_Rejection_Blocks_Further_Release()
    {
        using var dbContext = QaDbContextFactory.CreateContext();

        var partyService = new PartyOnboardingService(new BusinessPartyRepository(dbContext));
        var salesOrderService = new SalesOrderService(new SalesOrderRepository(dbContext));
        var dispatchService = new DispatchOrderService(new DispatchOrderRepository(dbContext));

        var customer = partyService.CreateParty(new CreateBusinessPartyRequest(
            PartyCode: "CUS-HOLD-001",
            PartyName: "Held Customer LLC",
            PartyType: "CUSTOMER",
            CountryCode: "EG",
            FirmNationalId: null,
            TaxRegistrationNumber: "445566778",
            TaxBranchCode: "010",
            Email: "held.customer@example.local",
            MobileNumber: "+201555555555",
            AddressLine1: "Giza",
            CreditLimitAmount: 10000m,
            CreditTermsDays: 30,
            IsCreditHoldEnabled: true,
            CreditHoldReason: "Account blocked pending collections review."));

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

        var submittedOrder = salesOrderService.SubmitSalesOrder(salesOrder.SalesOrderId, 2);
        Assert.Equal("ON_HOLD", submittedOrder.CreditStatusCode);
        Assert.Contains("collections review", submittedOrder.CreditHoldReason, StringComparison.OrdinalIgnoreCase);

        var rejectedOrder = salesOrderService.ReleaseCreditHold(
            submittedOrder.SalesOrderId,
            new ReleaseSalesOrderCreditHoldRequest(
                ActorUserId: 4,
                DecisionCode: "REJECT",
                Notes: "Finance checker rejected the release request."));

        Assert.Equal("REJECTED", rejectedOrder.CreditStatusCode);
        Assert.Equal("REJECTED", rejectedOrder.CreditReleaseDecisionCode);
        Assert.Equal("REJECTED", rejectedOrder.StatusCode);

        Assert.Throws<InvalidOperationException>(() =>
            dispatchService.CreateDispatchOrder(new CreateDispatchOrderRequest(
                BranchId: 1,
                SalesOrderId: rejectedOrder.SalesOrderId,
                DispatchWarehouseId: 1,
                PlannedShipAtUtc: new DateTime(2026, 4, 28, 11, 0, 0, DateTimeKind.Utc),
                Lines:
                [
                    new CreateDispatchOrderLineRequest(rejectedOrder.Lines.First().SalesOrderLineId, 1001, 2m, null, "BATCH-HOLD-RJ", null)
                ])));
    }

    [Fact]
    public void Customer_Credit_Control_Uses_Base_Currency_Exposure_Across_Multiple_Currencies()
    {
        using var dbContext = QaDbContextFactory.CreateContext();

        var partyService = new PartyOnboardingService(new BusinessPartyRepository(dbContext));
        var salesOrderService = new SalesOrderService(new SalesOrderRepository(dbContext));
        var invoiceService = new AccountsReceivableInvoiceService(new AccountsReceivableInvoiceRepository(dbContext));

        var customer = partyService.CreateParty(new CreateBusinessPartyRequest(
            PartyCode: "CUS-MULTI-001",
            PartyName: "Multi Currency Customer",
            PartyType: "CUSTOMER",
            CountryCode: "EG",
            FirmNationalId: null,
            TaxRegistrationNumber: "889977665",
            TaxBranchCode: "020",
            Email: "multi.currency@example.local",
            MobileNumber: "+201444444444",
            AddressLine1: "Cairo",
            CreditLimitAmount: 300m,
            CreditTermsDays: 30,
            IsCreditHoldEnabled: false,
            CreditHoldReason: null));

        var postedUsdInvoice = AccountsReceivableWorkflowTestHelper.SubmitApproveAndPostInvoice(
            invoiceService,
            invoiceService.CreateInvoice(new CreateAccountsReceivableInvoiceRequest(
                CustomerId: customer.PartyId,
                SalesOrderId: 1,
                DispatchOrderId: 1,
                InvoiceType: "TAX",
                InvoiceDate: new DateOnly(2026, 4, 18),
                TaxDate: new DateOnly(2026, 4, 18),
                DueDate: new DateOnly(2026, 5, 18),
                CurrencyCode: "USD",
                Lines:
                [
                    new CreateAccountsReceivableInvoiceLineRequest(1, 1001, 1m, "EA", 10m, "VAT14")
                ])).AccountsReceivableInvoiceId);

        var creditControl = salesOrderService.GetCustomerCreditControl(customer.PartyId, "EGP", new DateOnly(2026, 4, 20));

        Assert.Equal("EGP", creditControl.BaseCurrencyCode);
        Assert.True(creditControl.ExposureIncludesMultipleCurrencies);
        Assert.Equal(0m, creditControl.OutstandingReceivablesAmount);
        Assert.Equal(Math.Round(postedUsdInvoice.TotalAmount * 49.5m, 2, MidpointRounding.AwayFromZero), creditControl.OutstandingReceivablesBaseAmount);
        Assert.True(creditControl.ShouldHoldNewOrders);
        Assert.Contains("EGP", creditControl.HoldReason, StringComparison.OrdinalIgnoreCase);

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

        var submittedOrder = salesOrderService.SubmitSalesOrder(salesOrder.SalesOrderId, 2);
        Assert.Equal("ON_HOLD", submittedOrder.CreditStatusCode);
        Assert.True(submittedOrder.CreditExposureAmountAtCheck > 300m);
    }
}
