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

namespace CashLineERP.Api.Tests;

public sealed class PaymentProviderExecutionServiceQaTests
{
    [Fact]
    public void Customer_Refund_Can_Be_Queued_Dispatched_And_Settled_From_Callback()
    {
        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 executionService = new PaymentGatewayExecutionService(new PaymentGatewayExecutionRepository(dbContext));
        var gatewayCallbackService = new GatewayCallbackService(new GatewayCallbackRepository(dbContext));
        var settlementService = new PaymentSettlementBatchService(new PaymentSettlementBatchRepository(dbContext));

        var outbound = CreateAndActivateEndpoint(integrationService, "Paymob refund outbound", "PAYMOB", "OUTBOUND", "/refunds");
        var inbound = CreateAndActivateEndpoint(integrationService, "Paymob refund callback", "PAYMOB", "INBOUND", "/refund-callback");
        var channel = CreateAndActivateRefundChannel(paymentChannelService, outbound, inbound);
        var postedRefund = CreatePostedRefund(receiptService, refundService, channel.PaymentChannelConfigurationId, 80m, "PAYEXEC-QA-0001");

        var queued = executionService.QueueCustomerRefund(postedRefund.CustomerRefundId, 4);
        Assert.Equal("QUEUED", queued.StatusCode);

        var dispatched = executionService.DispatchPendingExecutions(new DispatchPaymentGatewayExecutionBatchRequest("PAYMENT_EXEC_WORKER", 10, channel.PaymentChannelConfigurationId));
        Assert.Single(dispatched.Executions);
        Assert.Equal("DISPATCHED", dispatched.Executions.Single().StatusCode);
        Assert.NotNull(dispatched.Executions.Single().ProviderReference);

        var payloadText = $$"""
        {
          "correlationKey": "{{queued.CorrelationKey}}",
          "providerReference": "{{dispatched.Executions.Single().ProviderReference}}",
          "settlementStatus": "SETTLED",
          "settlementDate": "2026-05-18",
          "settledAmount": 80.00,
          "feeAmount": 2.00,
          "netAmount": 78.00,
          "externalBatchReference": "PAYMOB-SETTLE-CB-001"
        }
        """;
        var signature = GatewayCallbackSecurity.ComputeSignature("secret://paymob/uat/key", payloadText, "PAYEXEC-CB-0001", queued.CorrelationKey);

        var received = gatewayCallbackService.Receive(new GatewayCallbackReceiveRequest(
            channel.PaymentChannelConfigurationId,
            signature,
            "PAYEXEC-CB-0001",
            queued.CorrelationKey,
            "SETTLEMENT_STATUS",
            payloadText));

        Assert.Equal("ACCEPTED", received.DecisionCode);

        var processed = gatewayCallbackService.ProcessCallbacks(channel.PaymentChannelConfigurationId, new ProcessGatewayCallbackBatchRequest("CALLBACK_WORKER", 10));
        Assert.Equal(1, processed.ProcessedCount);
        Assert.Equal(1, processed.UpdatedExecutionCount);
        Assert.Equal(1, processed.SettlementMutationCount);

        var refreshedExecution = executionService.GetExecution(queued.PaymentGatewayExecutionId);
        Assert.NotNull(refreshedExecution);
        Assert.Equal("SETTLED", refreshedExecution!.StatusCode);
        Assert.Equal("SETTLED", refreshedExecution.ProviderStatusCode);

        var batches = settlementService.GetBatches(channel.PaymentChannelConfigurationId);
        Assert.Single(batches);
        Assert.Single(batches.Single().Lines);
        Assert.Equal(0, batches.Single().OpenExceptionCount);
        Assert.Equal("SYNCED", batches.Single().Lines.Single().SettlementStatusCode);
    }

    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: "Payment provider execution QA endpoint"));

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

    private static PaymentChannelConfigurationResponse CreateAndActivateRefundChannel(
        PaymentChannelConfigurationService paymentChannelService,
        IntegrationEndpointResponse outbound,
        IntegrationEndpointResponse inbound)
    {
        var created = paymentChannelService.CreatePaymentChannel(new CreatePaymentChannelConfigurationRequest(
            ChannelName: "Paymob execution channel",
            BranchId: 1,
            IntegrationProviderCode: "PAYMOB",
            ChannelTypeCode: "CHECKOUT_LINK",
            UsageCode: "CUSTOMER_REFUND",
            CurrencyCode: "EGP",
            OutboundIntegrationEndpointId: outbound.IntegrationEndpointId,
            InboundIntegrationEndpointId: inbound.IntegrationEndpointId,
            SupportsCardTokenReferences: false,
            UsesWalletRuleEngine: false,
            RequiresCustomerReference: true,
            MinimumAmount: 1m,
            MaximumAmount: 5000m,
            SettlementLeadDays: 2,
            Notes: "Execution QA channel"));

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

    private static CustomerRefundResponse CreatePostedRefund(
        AccountsReceivableReceiptService receiptService,
        CustomerRefundService refundService,
        long paymentChannelConfigurationId,
        decimal amount,
        string referenceNumber)
    {
        var receipt = receiptService.CreateReceipt(new CreateAccountsReceivableReceiptRequest(
            CustomerId: 2,
            ReceiptDate: new DateOnly(2026, 5, 15),
            ReceiptMethod: "BANK_TRANSFER",
            CurrencyCode: "EGP",
            AmountAmount: amount + 15m,
            ReferenceNumber: $"{referenceNumber}-RCPT",
            Notes: "Execution 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, 16),
            RefundMethod: "PAYMENT_GATEWAY",
            CurrencyCode: "EGP",
            AmountAmount: amount,
            PaymentChannelConfigurationId: paymentChannelConfigurationId,
            ReferenceNumber: referenceNumber,
            Notes: "Execution QA refund"));

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