using CashLineERP.Application.Finance;
using CashLineERP.Application.Integration;
using CashLineERP.Contracts.Finance;
using CashLineERP.Contracts.Integration;
using CashLineERP.Infrastructure.Persistence.Repositories;

namespace CashLineERP.Api.Tests;

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

        var integrationService = new IntegrationEndpointRegistryService(new IntegrationEndpointRegistryRepository(dbContext));
        var paymentChannelService = new PaymentChannelConfigurationService(new PaymentChannelConfigurationRepository(dbContext));
        var refundService = new CustomerRefundService(new CustomerRefundRepository(dbContext), new PaymentGatewayExecutionService(new PaymentGatewayExecutionRepository(dbContext)));
        var receiptService = new AccountsReceivableReceiptService(new AccountsReceivableReceiptRepository(dbContext), refundService);
        var settlementService = new PaymentSettlementBatchService(new PaymentSettlementBatchRepository(dbContext));

        var channel = CreateAndActivateRefundChannel(integrationService, paymentChannelService);
        var postedRefund = CreatePostedRefund(receiptService, refundService, channel.PaymentChannelConfigurationId, 95m, "SET-BATCH-QA-0001");

        var createdBatch = settlementService.CreateBatch(new CreatePaymentSettlementBatchRequest(
            PaymentChannelConfigurationId: channel.PaymentChannelConfigurationId,
            SettlementDate: new DateOnly(2026, 5, 16),
            ExternalBatchReference: "PAYMOB-SETTLE-001",
            Lines:
            [
                new CreatePaymentSettlementBatchLineRequest(
                    CustomerRefundId: postedRefund.CustomerRefundId,
                    SettledAmount: 95m,
                    FeeAmount: 2m,
                    NetAmount: 93m,
                    ProviderReference: "PMB-SET-0001",
                    Notes: "Provider batch settlement")
            ]));

        Assert.Equal(0, createdBatch.OpenExceptionCount);
        Assert.Single(createdBatch.Lines);
        Assert.Equal("MATCHED", createdBatch.Lines.Single().SettlementStatusCode);

        var submitted = settlementService.SubmitBatch(createdBatch.PaymentSettlementBatchId, 3);
        var approved = settlementService.ApproveBatch(createdBatch.PaymentSettlementBatchId, 4);
        var posted = settlementService.PostBatch(createdBatch.PaymentSettlementBatchId, 4);

        Assert.Equal("SUBMITTED", submitted.StatusCode);
        Assert.Equal("APPROVED", approved.StatusCode);
        Assert.Equal("POSTED", posted.StatusCode);
    }

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

        var integrationService = new IntegrationEndpointRegistryService(new IntegrationEndpointRegistryRepository(dbContext));
        var paymentChannelService = new PaymentChannelConfigurationService(new PaymentChannelConfigurationRepository(dbContext));
        var refundService = new CustomerRefundService(new CustomerRefundRepository(dbContext), new PaymentGatewayExecutionService(new PaymentGatewayExecutionRepository(dbContext)));
        var receiptService = new AccountsReceivableReceiptService(new AccountsReceivableReceiptRepository(dbContext), refundService);
        var settlementService = new PaymentSettlementBatchService(new PaymentSettlementBatchRepository(dbContext));

        var channel = CreateAndActivateRefundChannel(integrationService, paymentChannelService);
        var postedRefund = CreatePostedRefund(receiptService, refundService, channel.PaymentChannelConfigurationId, 140m, "SET-BATCH-QA-0002");

        var createdBatch = settlementService.CreateBatch(new CreatePaymentSettlementBatchRequest(
            PaymentChannelConfigurationId: channel.PaymentChannelConfigurationId,
            SettlementDate: new DateOnly(2026, 5, 17),
            ExternalBatchReference: "PAYMOB-SETTLE-002",
            Lines:
            [
                new CreatePaymentSettlementBatchLineRequest(
                    CustomerRefundId: postedRefund.CustomerRefundId,
                    SettledAmount: 135m,
                    FeeAmount: 3m,
                    NetAmount: 132m,
                    ProviderReference: null,
                    Notes: "Variance import")
            ]));

        Assert.Equal(2, createdBatch.OpenExceptionCount);

        settlementService.SubmitBatch(createdBatch.PaymentSettlementBatchId, 3);
        settlementService.ApproveBatch(createdBatch.PaymentSettlementBatchId, 4);

        var postException = Assert.Throws<InvalidOperationException>(() =>
            settlementService.PostBatch(createdBatch.PaymentSettlementBatchId, 4));
        Assert.Contains("open settlement exceptions", postException.Message, StringComparison.OrdinalIgnoreCase);

        var openExceptions = settlementService.GetExceptions(createdBatch.PaymentSettlementBatchId, "OPEN");
        Assert.Equal(2, openExceptions.Count);

        var confirmed = settlementService.ConfirmException(openExceptions.Single(entry => entry.ExceptionCode == "MISSING_PROVIDER_REFERENCE").PaymentSettlementExceptionId, 4, new ResolvePaymentSettlementExceptionRequest("Provider reference reviewed offline"));
        var overridden = settlementService.OverrideException(openExceptions.Single(entry => entry.ExceptionCode == "AMOUNT_VARIANCE").PaymentSettlementExceptionId, 4, new ResolvePaymentSettlementExceptionRequest("Provider variance accepted by finance"));

        Assert.Equal("RESOLVED", confirmed.StatusCode);
        Assert.Equal("CONFIRMED", confirmed.ResolutionCode);
        Assert.Equal("RESOLVED", overridden.StatusCode);
        Assert.Equal("OVERRIDDEN", overridden.ResolutionCode);

        var refreshedBatch = settlementService.GetBatch(createdBatch.PaymentSettlementBatchId);
        Assert.NotNull(refreshedBatch);
        Assert.Equal(0, refreshedBatch!.OpenExceptionCount);
        Assert.Equal(2, refreshedBatch.ResolvedExceptionCount);

        var posted = settlementService.PostBatch(createdBatch.PaymentSettlementBatchId, 4);
        Assert.Equal("POSTED", posted.StatusCode);
    }

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

        var integrationService = new IntegrationEndpointRegistryService(new IntegrationEndpointRegistryRepository(dbContext));
        var paymentChannelService = new PaymentChannelConfigurationService(new PaymentChannelConfigurationRepository(dbContext));
        var refundService = new CustomerRefundService(new CustomerRefundRepository(dbContext), new PaymentGatewayExecutionService(new PaymentGatewayExecutionRepository(dbContext)));
        var receiptService = new AccountsReceivableReceiptService(new AccountsReceivableReceiptRepository(dbContext), refundService);
        var settlementService = new PaymentSettlementBatchService(new PaymentSettlementBatchRepository(dbContext));

        var channel = CreateAndActivateRefundChannel(integrationService, paymentChannelService, "USD");
        var postedRefund = CreatePostedRefund(receiptService, refundService, channel.PaymentChannelConfigurationId, 10m, "SET-BATCH-USD-0001", "USD");

        var createdBatch = settlementService.CreateBatch(new CreatePaymentSettlementBatchRequest(
            PaymentChannelConfigurationId: channel.PaymentChannelConfigurationId,
            SettlementDate: new DateOnly(2026, 5, 16),
            ExternalBatchReference: "PAYMOB-SETTLE-USD-001",
            Lines:
            [
                new CreatePaymentSettlementBatchLineRequest(
                    CustomerRefundId: postedRefund.CustomerRefundId,
                    SettledAmount: 10m,
                    FeeAmount: 1m,
                    NetAmount: 9m,
                    ProviderReference: "PMB-SET-USD-0001",
                    Notes: "Provider batch settlement USD")
            ]));

        Assert.Equal("USD", createdBatch.CurrencyCode);
        Assert.Equal("EGP", createdBatch.BaseCurrencyCode);
        Assert.Equal(49.50m, createdBatch.FxRateToBase);
        Assert.Equal(495m, createdBatch.TotalInternalBaseAmount);
        Assert.Equal(495m, createdBatch.TotalSettledBaseAmount);
        Assert.Equal(49.50m, createdBatch.TotalFeeBaseAmount);
        Assert.Equal(445.50m, createdBatch.TotalNetBaseAmount);
        var line = Assert.Single(createdBatch.Lines);
        Assert.Equal(495m, line.InternalBaseAmount);
        Assert.Equal(495m, line.SettledBaseAmount);
        Assert.Equal(49.50m, line.FeeBaseAmount);
        Assert.Equal(445.50m, line.NetBaseAmount);
    }

    private static PaymentChannelConfigurationResponse CreateAndActivateRefundChannel(
        IntegrationEndpointRegistryService integrationService,
        PaymentChannelConfigurationService paymentChannelService,
        string currencyCode = "EGP")
    {
        var outbound = CreateAndActivateEndpoint(integrationService, "Paymob refund outbound", "PAYMOB", "OUTBOUND", "/refunds");
        var inbound = CreateAndActivateEndpoint(integrationService, "Paymob refund callback", "PAYMOB", "INBOUND", "/refund-callback");

        var created = paymentChannelService.CreatePaymentChannel(new CreatePaymentChannelConfigurationRequest(
            ChannelName: "Paymob refund channel",
            BranchId: 1,
            IntegrationProviderCode: "PAYMOB",
            ChannelTypeCode: "CHECKOUT_LINK",
            UsageCode: "CUSTOMER_REFUND",
            CurrencyCode: currencyCode,
            OutboundIntegrationEndpointId: outbound.IntegrationEndpointId,
            InboundIntegrationEndpointId: inbound.IntegrationEndpointId,
            SupportsCardTokenReferences: false,
            UsesWalletRuleEngine: false,
            RequiresCustomerReference: true,
            MinimumAmount: 1m,
            MaximumAmount: 5000m,
            SettlementLeadDays: 2,
            Notes: "Refund settlement channel"));

        paymentChannelService.SubmitPaymentChannel(created.PaymentChannelConfigurationId, 3);
        paymentChannelService.ApprovePaymentChannel(created.PaymentChannelConfigurationId, 4);
        return paymentChannelService.ActivatePaymentChannel(created.PaymentChannelConfigurationId, 4);
    }

    private static IntegrationEndpointResponse CreateAndActivateEndpoint(
        IntegrationEndpointRegistryService integrationService,
        string endpointName,
        string providerCode,
        string directionCode,
        string relativePath)
    {
        var created = integrationService.CreateIntegrationEndpoint(new CreateIntegrationEndpointRequest(
            EndpointName: endpointName,
            IntegrationProviderCode: providerCode,
            EnvironmentCode: "UAT",
            DirectionCode: directionCode,
            TransportCode: "REST",
            HttpMethodCode: "POST",
            BaseUrl: "https://gateway.uat.example",
            RelativePath: relativePath,
            AuthenticationModeCode: "SIGNATURE",
            CredentialReference: $"secret://{providerCode.ToLowerInvariant()}/uat/key",
            TimeoutSeconds: 20,
            RetryPolicyCode: "STANDARD",
            MaxRetryCount: 2,
            IsIdempotencyKeyRequired: true,
            Notes: "Settlement QA endpoint"));

        integrationService.SubmitIntegrationEndpoint(created.IntegrationEndpointId, 3);
        integrationService.ApproveIntegrationEndpoint(created.IntegrationEndpointId, 4);
        return integrationService.ActivateIntegrationEndpoint(created.IntegrationEndpointId, 4);
    }

    private static CustomerRefundResponse CreatePostedRefund(
        AccountsReceivableReceiptService receiptService,
        CustomerRefundService refundService,
        long paymentChannelConfigurationId,
        decimal amount,
        string referenceNumber,
        string currencyCode = "EGP")
    {
        var receipt = receiptService.CreateReceipt(new CreateAccountsReceivableReceiptRequest(
            CustomerId: 2,
            ReceiptDate: new DateOnly(2026, 5, 15),
            ReceiptMethod: "BANK_TRANSFER",
            CurrencyCode: currencyCode,
            AmountAmount: amount + 20m,
            ReferenceNumber: $"{referenceNumber}-RCPT",
            Notes: "Settlement source receipt",
            Allocations: []));

        receiptService.SubmitReceipt(receipt.AccountsReceivableReceiptId, 3);
        receiptService.ApproveReceipt(receipt.AccountsReceivableReceiptId, 4);
        var postedReceipt = receiptService.PostReceipt(receipt.AccountsReceivableReceiptId, 4);

        var refund = refundService.CreateRefund(new CreateCustomerRefundRequest(
            CustomerId: 2,
            SourceAccountsReceivableReceiptId: postedReceipt.AccountsReceivableReceiptId,
            RefundDate: new DateOnly(2026, 5, 15),
            RefundMethod: "PAYMENT_GATEWAY",
            CurrencyCode: currencyCode,
            AmountAmount: amount,
            PaymentChannelConfigurationId: paymentChannelConfigurationId,
            ReferenceNumber: referenceNumber,
            Notes: "Settlement QA refund"));

        refundService.SubmitRefund(refund.CustomerRefundId, 3);
        refundService.ApproveRefund(refund.CustomerRefundId, 4);
        return refundService.PostRefund(refund.CustomerRefundId, 4);
    }
}
