<?php

namespace App\Payment\Controller;

use App\Appointment\Entity\Appointment;
use App\Appointment\Repository\AppointmentRepository;
use App\Auth\Entity\User;
use App\Payment\Entity\Payment;
use App\Config\Repository\SiteConfigRepository;
use App\Payment\Gateway\GatewayFactory;
use App\Payment\Repository\PaymentRepository;
use App\Payment\Service\PaymentManager;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[OA\Tag(name: 'Payments')]
class PaymentController extends BaseController
{
    // Shaparak payment network callback IP ranges
    private const ALLOWED_CALLBACK_IPS = [
        '91.92.0.0/16',
        '195.146.32.0/22',
    ];

    public function __construct(
        private readonly PaymentRepository     $paymentRepo,
        private readonly AppointmentRepository $appointmentRepo,
        private readonly GatewayFactory        $gateways,
        private readonly PaymentManager        $paymentManager,
        private readonly SiteConfigRepository  $configRepo,
        private readonly string                $appBaseUrl,
        private readonly string                $allowedFrontendHosts = '',
    ) {}

    // ── Appointment Payment ───────────────────────────────────────────────────

    #[OA\Post(
        path: '/api/v1/payment/appointment',
        summary: 'Initiate an appointment payment',
        security: [['bearerAuth' => []]],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['appointment_uuid', 'gateway'],
                properties: [
                    new OA\Property(property: 'appointment_uuid', type: 'string', format: 'uuid'),
                    new OA\Property(property: 'gateway', type: 'string', enum: ['mellat', 'sep']),
                    new OA\Property(property: 'frontend_address', type: 'string', format: 'uri', nullable: true),
                ]
            )
        ),
        responses: [
            new OA\Response(
                response: 200,
                description: 'Payment initiated',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(
                            property: 'data',
                            properties: [
                                new OA\Property(property: 'payment_uuid', type: 'string', format: 'uuid'),
                                new OA\Property(property: 'pay_url', type: 'string', format: 'uri', description: 'مرورگر به این آدرس بک‌اند هدایت شود؛ بک‌اند به درگاه منتقل می‌کند'),
                                new OA\Property(property: 'order_id', type: 'string'),
                            ],
                            type: 'object'
                        ),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 422, description: 'Validation error'),
        ]
    )]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    #[Route('/api/v1/payment/appointment', methods: ['POST'])]
    public function initiateAppointment(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $data            = json_decode($request->getContent(), true) ?? [];
        $appointmentUuid = trim($data['appointment_uuid'] ?? '');
        $gatewayName     = trim($data['gateway'] ?? 'mellat');
        $frontendAddress = trim($data['frontend_address'] ?? '');

        $appointment = $this->appointmentRepo->findByUuid($appointmentUuid);
        if ($appointment === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نوبت یافت نشد', 404);
        }

        if ($appointment->getUser()->getId() !== $user->getId()) {
            return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
        }

        if (!in_array($appointment->getStatus(), [Appointment::STATUS_PENDING, Appointment::STATUS_CONFIRMED], true)) {
            return $this->error(ErrorCodes::ERR_PAYMENT_003, ErrorCodes::message(ErrorCodes::ERR_PAYMENT_003), 422);
        }

        // Validate Open Redirect
        if (!empty($frontendAddress) && !$this->isAllowedFrontend($frontendAddress)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'آدرس بازگشت مجاز نیست', 422, 'frontend_address');
        }

        // اعتبارسنجی اولیهٔ درگاه (فعال/معتبر بودن)؛ ارتباط با بانک اینجا انجام
        // نمی‌شود — در GET /payment/pay هنگام انتقال مرورگر به درگاه انجام می‌گیرد.
        if ($this->gateways->resolve($gatewayName) === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'درگاه پرداخت نامعتبر یا غیرفعال است', 422, 'gateway');
        }

        $feeRials = (int) $this->configRepo->get('appointment_fee_rials');
        $payment = new Payment($user, $feeRials, $gatewayName, Payment::TYPE_APPOINTMENT, $frontendAddress);
        $payment->setAppointment($appointment);
        $this->paymentRepo->save($payment);

        // مرورگر به این endpoint بک‌اند می‌رود؛ آنجا صلاحیت نهایی + ارتباط با بانک
        // + انتقال به درگاه انجام می‌شود. کلاینت هرگز مستقیم به بانک ریکوست نمی‌زند.
        return $this->success([
            'payment_uuid' => $payment->getUuid(),
            'pay_url'      => $this->appBaseUrl . '/api/v1/payment/pay/' . $payment->getOrderId(),
            'order_id'     => $payment->getOrderId(),
        ]);
    }

    // ── Pure-redirect entry (no XHR) — browser navigates here to start payment ──

    #[OA\Get(
        path: '/api/v1/payment/order/{appointmentUuid}',
        summary: 'Start an appointment payment via a pure browser redirect (no XHR)',
        parameters: [
            new OA\Parameter(name: 'appointmentUuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
            new OA\Parameter(name: 'gateway', in: 'query', required: true, schema: new OA\Schema(type: 'string')),
            new OA\Parameter(name: 'return', in: 'query', required: false, schema: new OA\Schema(type: 'string', format: 'uri')),
        ],
        responses: [
            new OA\Response(response: 302, description: 'Redirect to the bank, or back to return URL with status on validation failure'),
        ]
    )]
    #[Route('/api/v1/payment/order/{appointmentUuid}', methods: ['GET'])]
    public function startOrderPayment(string $appointmentUuid, Request $request): \Symfony\Component\HttpFoundation\Response
    {
        $gatewayName = trim((string) $request->query->get('gateway', ''));
        $return      = trim((string) $request->query->get('return', ''));

        if ($return !== '' && !$this->isAllowedFrontend($return)) {
            return $this->renderPaymentResult('invalid_return');
        }

        $appointment = $this->appointmentRepo->findByUuid($appointmentUuid);
        if ($appointment === null) {
            return $this->redirectToReturn($return, 'notfound');
        }

        // نوبتِ منقضی (status=expired یا پنجرهٔ ۱۵ دقیقه‌ای گذشته) → غیرقابل‌پرداخت.
        if ($appointment->getStatus() === Appointment::STATUS_EXPIRED
            || ($appointment->getStatus() === Appointment::STATUS_PENDING
                && $appointment->isPaymentWindowExpired(time()))) {
            $this->expireAppointmentPayment($appointment);
            return $this->redirectToReturn($return, 'expired');
        }

        // اعتبارسنجی سفارش: فقط نوبت قابل‌پرداخت.
        if (!in_array($appointment->getStatus(), [Appointment::STATUS_PENDING, Appointment::STATUS_CONFIRMED], true)) {
            return $this->redirectToReturn($return, 'invalid');
        }

        if ($this->gateways->resolve($gatewayName) === null) {
            return $this->redirectToReturn($return, 'gateway');
        }

        // جلوگیری از pending تکراری: پرداخت pending موجود را ادامه بده وگرنه بساز.
        $payment = $this->paymentRepo->findPendingByAppointment($appointment)
            ?? $this->createAppointmentPayment($appointment, $gatewayName, $return);

        // آدرس بازگشت را روی پرداختِ بازاستفاده‌شده هم به‌روزرسانی کن تا بازگشت درست باشد.
        if ($return !== '' && $payment->getFrontendAddress() !== $return) {
            $payment->setFrontendAddress($return);
            $this->paymentRepo->save($payment);
        }

        // ارتباط با بانک + انتقال به شاپرک — همان مسیر واحد.
        $result = $this->paymentManager->startGatewayHandoff($payment);
        if ($result === false) {
            return $this->redirectToFrontend($payment, false);
        }

        return $result->redirectMethod === 'POST'
            ? $this->autoSubmitForm(strtok($result->redirectUrl, '?'), $result->redirectParams)
            : new RedirectResponse($result->redirectUrl);
    }

    private function createAppointmentPayment(Appointment $appointment, string $gatewayName, string $return): Payment
    {
        $feeRials = (int) $this->configRepo->get('appointment_fee_rials');
        $payment  = new Payment($appointment->getUser(), $feeRials, $gatewayName, Payment::TYPE_APPOINTMENT, $return);
        $payment->setAppointment($appointment);
        $this->paymentRepo->save($payment);
        return $payment;
    }

    /** نوبتِ منقضی را expired و پرداخت pending آن را canceled می‌کند (idempotent). */
    private function expireAppointmentPayment(Appointment $appointment): void
    {
        if ($appointment->getStatus() === Appointment::STATUS_PENDING
            && $appointment->canTransitionTo(Appointment::STATUS_EXPIRED)) {
            $appointment->transitionTo(Appointment::STATUS_EXPIRED);
            $this->appointmentRepo->save($appointment);
        }
        $payment = $this->paymentRepo->findPendingByAppointment($appointment);
        if ($payment !== null) {
            $payment->setStatus(Payment::STATUS_CANCELED);
            $this->paymentRepo->save($payment);
        }
    }

    private function redirectToReturn(string $return, string $status): \Symfony\Component\HttpFoundation\Response
    {
        if ($return !== '' && $this->isAllowedFrontend($return)) {
            $sep = str_contains($return, '?') ? '&' : '?';
            return new RedirectResponse($return . $sep . 'status=' . $status);
        }
        return $this->renderPaymentResult($status);
    }

    // ── Gateway hand-off (public — browser redirect to the bank) ───────────────

    #[OA\Get(
        path: '/api/v1/payment/pay/{orderId}',
        summary: 'Redirect the browser to the payment gateway for a pending payment',
        parameters: [
            new OA\Parameter(name: 'orderId', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
        ],
        responses: [
            new OA\Response(response: 302, description: 'Redirect (GET gateway) or auto-submitting POST form (POST gateway)'),
            new OA\Response(response: 404, description: 'Payment not found'),
        ]
    )]
    #[Route('/api/v1/payment/pay/{orderId}', methods: ['GET'])]
    public function pay(string $orderId): \Symfony\Component\HttpFoundation\Response
    {
        $payment = $this->paymentRepo->findByOrderId($orderId);
        if ($payment === null) {
            return $this->renderPaymentResult('notfound');
        }

        // پرداختِ نوبت: اگر نوبت منقضی شده باشد، پرداخت هم منقضی و غیرقابل‌پرداخت است.
        $appointment = $payment->getAppointment();
        if ($payment->getType() === Payment::TYPE_APPOINTMENT && $appointment !== null
            && ($appointment->getStatus() === Appointment::STATUS_EXPIRED
                || ($appointment->getStatus() === Appointment::STATUS_PENDING
                    && $appointment->isPaymentWindowExpired(time())))) {
            $this->expireAppointmentPayment($appointment);
            return $this->renderPaymentResult('expired', $payment);
        }

        // فقط پرداخت در انتظار قابل انتقال به درگاه است (جلوگیری از پرداخت تکراری/replay).
        if ($payment->getStatus() !== Payment::STATUS_PENDING) {
            return $this->redirectToFrontend($payment, $payment->getStatus() === Payment::STATUS_SUCCESS);
        }

        // ارتباط با بانک + init در سرویس انجام می‌شود؛ کنترلر فقط انتقال HTTP را می‌سازد.
        $result = $this->paymentManager->startGatewayHandoff($payment);
        if ($result === false) {
            return $this->redirectToFrontend($payment, false);
        }

        // انتقال مرورگر به درگاه: 302 برای درگاه GET (سپ/mock) یا فرم auto-submit POST (ملت).
        if ($result->redirectMethod === 'POST') {
            return $this->autoSubmitForm(strtok($result->redirectUrl, '?'), $result->redirectParams);
        }

        return new RedirectResponse($result->redirectUrl);
    }

    /** صفحهٔ انتقال به درگاه (Twig) با فرمِ auto-submit به‌صورت POST. */
    private function autoSubmitForm(string $action, array $params): \Symfony\Component\HttpFoundation\Response
    {
        return $this->render('payment/redirect.html.twig', [
            'action' => $action,
            'params' => $params,
        ]);
    }

    // ── Payment Callback (public — no JWT) ───────────────────────────────────

    #[OA\Post(
        path: '/api/v1/payment/callback/{gateway}',
        summary: 'Payment gateway callback (public, IP-restricted)',
        parameters: [
            new OA\Parameter(
                name: 'gateway',
                in: 'path',
                required: true,
                schema: new OA\Schema(type: 'string', enum: ['mellat', 'sep'])
            ),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Callback processed — either a redirect or JSON result',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean'),
                        new OA\Property(property: 'payment', type: 'object'),
                    ]
                )
            ),
            new OA\Response(response: 302, description: 'Redirect to frontend with payment result'),
            new OA\Response(response: 403, description: 'Forbidden — IP not in allowed Shaparak ranges'),
            new OA\Response(response: 404, description: 'Payment not found'),
        ]
    )]
    #[Route('/api/v1/payment/callback/{gateway}', methods: ['POST', 'GET'])]
    public function callback(string $gateway, Request $request): \Symfony\Component\HttpFoundation\Response
    {
        $clientIp = $request->getClientIp() ?? '';
        // درگاه‌های ملت و سپ نتیجه را با ریدایرکتِ مرورگرِ کاربر (POST/GET) برمی‌گردانند،
        // نه server-to-server؛ پس IP دریافتی، IPِ کاربر است و allowlist شاپرک اعمال نمی‌شود
        // (در غیر این صورت هر callback واقعی — از جمله «لغو» — رد می‌شد). امنیت از طریق چک
        // ضد-دستکاری (RefId/SaleOrderId) و verify سمت بانک در PaymentManager تأمین می‌شود.
        $browserRedirectGateways = ['mellat', 'sep'];
        $bypassIp = $this->gateways->isTestMode() || in_array($gateway, $browserRedirectGateways, true);
        if (!$bypassIp && !$this->isAllowedCallbackIp($clientIp)) {
            return $this->renderPaymentResult('forbidden');
        }

        $callbackData = array_merge($request->query->all(), $request->request->all());
        $orderId      = $callbackData['order_id'] ?? $callbackData['ResNum'] ?? '';

        // verify امن (transaction + قفل + idempotent + post-action + log) در سرویس.
        $payment = $this->paymentManager->processCallback($gateway, $callbackData, $clientIp, $orderId);
        if ($payment === null) {
            return $this->renderPaymentResult('notfound');
        }

        return $this->redirectToFrontend($payment, $payment->getStatus() === Payment::STATUS_SUCCESS);
    }

    // ── Subscription Payment ──────────────────────────────────────────────────

    #[OA\Post(
        path: '/api/v1/subscription-payment',
        summary: 'Initiate a subscription payment',
        security: [['bearerAuth' => []]],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['gateway', 'amount_rials'],
                properties: [
                    new OA\Property(property: 'gateway', type: 'string', enum: ['mellat', 'sep']),
                    new OA\Property(property: 'frontend_address', type: 'string', format: 'uri', nullable: true),
                    new OA\Property(property: 'amount_rials', type: 'integer', minimum: 1),
                ]
            )
        ),
        responses: [
            new OA\Response(
                response: 200,
                description: 'Subscription payment initiated',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(
                            property: 'data',
                            properties: [
                                new OA\Property(property: 'payment_uuid', type: 'string', format: 'uuid'),
                                new OA\Property(property: 'pay_url', type: 'string', format: 'uri'),
                                new OA\Property(property: 'order_id', type: 'string'),
                            ],
                            type: 'object'
                        ),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 422, description: 'Validation error'),
        ]
    )]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    #[Route('/api/v1/subscription-payment', methods: ['POST'])]
    public function initiateSubscription(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $data            = json_decode($request->getContent(), true) ?? [];
        $gatewayName     = trim($data['gateway'] ?? 'mellat');
        $frontendAddress = trim($data['frontend_address'] ?? '');
        $amountRials     = (int) ($data['amount_rials'] ?? 0);

        if ($amountRials <= 0) {
            return $this->error(ErrorCodes::ERR_PAYMENT_002, ErrorCodes::message(ErrorCodes::ERR_PAYMENT_002), 422);
        }

        if (!empty($frontendAddress) && !$this->isAllowedFrontend($frontendAddress)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'آدرس بازگشت مجاز نیست', 422, 'frontend_address');
        }

        if ($this->gateways->resolve($gatewayName) === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'درگاه پرداخت نامعتبر یا غیرفعال است', 422, 'gateway');
        }

        $periodUuid = trim($data['period_uuid'] ?? '');
        $payment    = new Payment($user, $amountRials, $gatewayName, Payment::TYPE_SUBSCRIPTION, $frontendAddress);
        if ($periodUuid !== '') {
            $payment->setMetadata(['period_uuid' => $periodUuid]);
        }
        $this->paymentRepo->save($payment);

        // مثل appointment: ارتباط با بانک اینجا نیست؛ در GET /payment/pay انجام می‌شود.
        return $this->success([
            'payment_uuid' => $payment->getUuid(),
            'pay_url'      => $this->appBaseUrl . '/api/v1/payment/pay/' . $payment->getOrderId(),
            'order_id'     => $payment->getOrderId(),
        ]);
    }

    #[OA\Post(
        path: '/api/v1/subscription-payment/callback/{gateway}',
        summary: 'Subscription payment gateway callback (public, IP-restricted)',
        parameters: [
            new OA\Parameter(
                name: 'gateway',
                in: 'path',
                required: true,
                schema: new OA\Schema(type: 'string', enum: ['mellat', 'sep'])
            ),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Callback processed — either a redirect or JSON result',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean'),
                        new OA\Property(property: 'payment', type: 'object'),
                    ]
                )
            ),
            new OA\Response(response: 302, description: 'Redirect to frontend with payment result'),
            new OA\Response(response: 403, description: 'Forbidden — IP not in allowed Shaparak ranges'),
            new OA\Response(response: 404, description: 'Payment not found'),
        ]
    )]
    #[Route('/api/v1/subscription-payment/callback/{gateway}', methods: ['POST', 'GET'])]
    public function subscriptionCallback(string $gateway, Request $request): \Symfony\Component\HttpFoundation\Response
    {
        return $this->callback($gateway, $request);
    }

    // ── Status ────────────────────────────────────────────────────────────────

    #[OA\Get(
        path: '/api/v1/payment/{uuid}',
        summary: 'Get payment status by UUID',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(
                name: 'uuid',
                in: 'path',
                required: true,
                schema: new OA\Schema(type: 'string', format: 'uuid')
            ),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Payment details',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(
                            property: 'data',
                            properties: [
                                new OA\Property(property: 'uuid', type: 'string', format: 'uuid'),
                                new OA\Property(property: 'status', type: 'string'),
                                new OA\Property(property: 'amount_rials', type: 'integer'),
                                new OA\Property(property: 'gateway', type: 'string'),
                                new OA\Property(property: 'reference_id', type: 'string', nullable: true),
                                new OA\Property(property: 'created_at', type: 'integer'),
                            ],
                            type: 'object'
                        ),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 403, description: 'Forbidden'),
            new OA\Response(response: 404, description: 'Payment not found'),
        ]
    )]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    #[Route('/api/v1/payment/config', methods: ['GET'])]
    public function config(): JsonResponse
    {
        return $this->success([
            'test_mode'             => $this->gateways->isTestMode(),
            'appointment_fee_rials' => (int) ($this->configRepo->get('appointment_fee_rials') ?: 0),
            'gateways'              => $this->gateways->activeGateways(),
        ]);
    }

    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    #[Route('/api/v1/my/payments', methods: ['GET'])]
    public function myPayments(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $page   = max(1, (int) $request->query->get('page', 1));
        $limit  = min(100, max(1, (int) $request->query->get('limit', 20)));
        $status = $request->query->get('status');

        $items = array_map(
            fn(Payment $p) => $p->toArray(),
            $this->paymentRepo->findByUser($user, $status, $page, $limit)
        );
        $total = $this->paymentRepo->countByUser($user, $status);

        return $this->paginated($items, $total, $page, $limit);
    }

    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    #[Route('/api/v1/payment/{uuid}', methods: ['GET'])]
    public function getStatus(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $payment = $this->paymentRepo->findByUuid($uuid);
        if ($payment === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پرداخت یافت نشد', 404);
        }

        if ($payment->getUser()->getId() !== $user->getId() && !$user->hasRole('ROLE_ADMIN')) {
            return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
        }

        return $this->success($payment->toArray());
    }

    // ── Private helpers ───────────────────────────────────────────────────────

    /** @return string[] allowed frontend hosts — from SiteConfig, falling back to env. */
    private function allowedHosts(): array
    {
        $fromConfig = (string) ($this->configRepo->get('payment_allowed_frontend_hosts') ?? '');
        $raw = $fromConfig !== '' ? $fromConfig : $this->allowedFrontendHosts;
        return array_filter(array_map('trim', explode(',', $raw)));
    }

    private function isAllowedFrontend(string $url): bool
    {
        $hosts = $this->allowedHosts();
        if (empty($hosts)) {
            return false;
        }
        $host = parse_url($url, PHP_URL_HOST);
        return in_array($host, $hosts, true);
    }

    private function isAllowedCallbackIp(string $ip): bool
    {
        if (empty($ip)) {
            return false;
        }
        foreach (self::ALLOWED_CALLBACK_IPS as $cidr) {
            [$subnet, $maskBits] = explode('/', $cidr);
            $maskBits = (int) $maskBits;
            $ipLong     = ip2long($ip);
            $subnetLong = ip2long($subnet);
            if ($ipLong === false || $subnetLong === false) {
                continue;
            }
            $mask = -1 << (32 - $maskBits);
            if (($ipLong & $mask) === ($subnetLong & $mask)) {
                return true;
            }
        }
        return false;
    }

    /** ثانیهٔ تأخیر پیش از ریدایرکت خودکار به فرانت‌اند در صفحهٔ نتیجه. */
    private const RESULT_REDIRECT_DELAY = 5;

    /** صفحهٔ نتیجهٔ پرداخت (Twig). اگر $redirectTo داده شود، بعد از چند ثانیه به آن می‌رود. */
    private function renderPaymentResult(string $status, ?Payment $payment = null, string $redirectTo = ''): \Symfony\Component\HttpFoundation\Response
    {
        $labels = [
            'success'        => ['پرداخت موفق', 'پرداخت شما با موفقیت انجام شد.'],
            'failed'         => ['پرداخت ناموفق', 'پرداخت انجام نشد. در صورت کسر وجه، مبلغ طی ۷۲ ساعت بازمی‌گردد.'],
            'canceled'       => ['پرداخت لغو شد', 'پرداخت توسط شما لغو شد.'],
            'pending'        => ['در انتظار پرداخت', 'این پرداخت هنوز نهایی نشده است.'],
            'notfound'       => ['سفارش یافت نشد', 'سفارش موردنظر یافت نشد.'],
            'invalid'        => ['قابل پرداخت نیست', 'این سفارش در وضعیت قابل پرداخت نیست.'],
            'gateway'        => ['درگاه نامعتبر', 'درگاه پرداخت انتخابی نامعتبر یا غیرفعال است.'],
            'invalid_return' => ['آدرس بازگشت نامعتبر', 'آدرس بازگشت مجاز نیست.'],
            'forbidden'      => ['دسترسی غیرمجاز', 'این درخواست از مبدأ مجاز ارسال نشده است.'],
            'expired'        => ['مهلت پرداخت به پایان رسید', 'مهلت ۱۵ دقیقه‌ایِ پرداخت این نوبت تمام شده است. لطفاً دوباره نوبت بگیرید.'],
        ];
        [$title, $message] = $labels[$status] ?? ['خطا در پرداخت', 'خطایی در فرآیند پرداخت رخ داد.'];

        return $this->render('payment/result.html.twig', [
            'status'      => $status,
            'title'       => $title,
            'message'     => $message,
            'payment'     => $payment?->toArray(),
            'redirect_to' => $redirectTo,
            'delay'       => self::RESULT_REDIRECT_DELAY,
        ]);
    }

    private function redirectToFrontend(Payment $payment, bool $success): \Symfony\Component\HttpFoundation\Response
    {
        $base = $payment->getFrontendAddress();
        if (empty($base)) {
            // آدرس بازگشتی نداریم → فقط صفحهٔ نتیجه (بدون ریدایرکت خودکار).
            return $this->renderPaymentResult($payment->getStatus(), $payment);
        }

        // صفحهٔ نتیجه را نشان بده و بعد از چند ثانیه به همان فرانت‌اندِ مبدأ برگرد.
        $sep = str_contains($base, '?') ? '&' : '?';
        $url = $base . $sep . 'payment_uuid=' . $payment->getUuid() . '&status=' . $payment->getStatus();
        return $this->renderPaymentResult($payment->getStatus(), $payment, $url);
    }
}
