<?php

namespace App\Tests\Payment;

use App\Config\Entity\SiteConfig;
use App\Payment\Entity\Payment;
use App\Tests\ApiTestCase;

/**
 * The payment callback must confirm an order only when the gateway-reported
 * settled amount matches what we charged. Guards against underpayment / a
 * replayed RefNum from another (cheaper) order marking an expensive order paid.
 */
class PaymentCallbackAmountTest extends ApiTestCase
{
    private function enableTestMode(): void
    {
        $cfg = $this->em->getRepository(SiteConfig::class)->findOneBy(['configKey' => 'payment_test_mode']);
        if ($cfg === null) {
            $cfg = new SiteConfig('payment_test_mode', '1');
            $this->em->persist($cfg);
        } else {
            $cfg->setValue('1');
        }
        $this->em->flush();
    }

    private function makePayment(int $amountRials): Payment
    {
        $user = $this->createUser();
        $payment = new Payment($user, $amountRials, 'mock', Payment::TYPE_SMS_WALLET);
        $this->em->persist($payment);
        $this->em->flush();

        return $payment;
    }

    private function fireCallback(Payment $payment, int $reportedAmount): void
    {
        $this->client->request('POST', '/api/v1/payment/callback/mock?' . http_build_query([
            'order_id'    => $payment->getOrderId(),
            'mock'        => '1',
            'ResCode'     => '0',
            'mock_amount' => (string) $reportedAmount,
        ]));
    }

    private function reload(Payment $payment): Payment
    {
        $this->em->clear();

        return $this->em->getRepository(Payment::class)->find($payment->getId());
    }

    public function testUnderpaymentIsRejected(): void
    {
        $this->enableTestMode();
        $payment = $this->makePayment(50000);

        $this->fireCallback($payment, 10000);

        $this->assertSame(Payment::STATUS_FAILED, $this->reload($payment)->getStatus());
    }

    public function testMatchingAmountSucceeds(): void
    {
        $this->enableTestMode();
        $payment = $this->makePayment(50000);

        $this->fireCallback($payment, 50000);

        $this->assertSame(Payment::STATUS_SUCCESS, $this->reload($payment)->getStatus());
    }

    public function testReplayedGatewayReferenceIsRejected(): void
    {
        $this->enableTestMode();

        // Unique per run — db_test is shared and not reset between runs.
        $ref = 'REF-' . bin2hex(random_bytes(8));

        $first = $this->makePayment(50000);
        $this->fireCallbackWithRef($first, $ref, 50000);
        $this->assertSame(Payment::STATUS_SUCCESS, $this->reload($first)->getStatus());

        // Same gateway reference replayed onto a different (same-amount) order.
        $second = $this->makePayment(50000);
        $this->fireCallbackWithRef($second, $ref, 50000);
        $this->assertSame(Payment::STATUS_FAILED, $this->reload($second)->getStatus());
    }

    private function fireCallbackWithRef(Payment $payment, string $refId, int $reportedAmount): void
    {
        $this->client->request('POST', '/api/v1/payment/callback/mock?' . http_build_query([
            'order_id'    => $payment->getOrderId(),
            'mock'        => '1',
            'ResCode'     => '0',
            'RefId'       => $refId,
            'mock_amount' => (string) $reportedAmount,
        ]));
    }
}
