<?php

namespace App\PaymentGateways\Razorpay;

use App\Contracts\PaymentGatewayContract;
use App\Models\Order;
use App\Services\CartService;
use App\Services\OrderService;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Razorpay\Api\Api;
use Throwable;

class RazorpayGateway implements PaymentGatewayContract
{
    protected Api $api;

    public function __construct(
        protected OrderService $orderService,
        protected CartService $cartService
    ) {
        $this->api = new Api(
            (string) setting('razorpay.key_id'),
            (string) setting('razorpay.key_secret')
        );
    }

    public function processPayment(Order $order)
    {
        $paymentId = request('razorpay_payment_id');
        $orderId = request('razorpay_order_id');
        $signature = request('razorpay_signature');

        if (!$paymentId || !$orderId || !$signature) {
            $order->update([
                'status' => getConstant('ORDER_STATUS_FAILED'),
                'payment_error' => 'Missing Razorpay payment details.',
            ]);

            return response()->json([
                'redirect_url' => route('ch_order_failed', [$order->encrypted_id]),
            ]);
        }

        try {
            $this->verifySignature($orderId, $paymentId, $signature);

            $payment = $this->api->payment->fetch($paymentId);

            if (($payment['status'] ?? null) !== 'captured') {
                $payment = $payment->capture([
                    'amount' => $payment['amount'],
                ]);
            }

            $expectedAmount = (int) round(((float) $order->total) * 100);
            $paidAmount = (int) ($payment['amount'] ?? 0);

            if ($paidAmount !== $expectedAmount) {
                throw new \RuntimeException('Paid amount does not match order total.');
            }

            if (($payment['status'] ?? null) === 'captured') {
                $status = setting('razorpay.order_status', getConstant('ORDER_STATUS_PROCESSING'));

                $order->update([
                    'transaction_id' => $paymentId,
                    'payment_error' => null,
                ]);

                $this->orderService->handlePostPaymentFlow($order, (int) $status);

                return response()->json([
                    'redirect_url' => route('ch_order_submitted', [$order->encrypted_id]),
                ]);
            }

            throw new \RuntimeException('Payment could not be captured.');
        } catch (Throwable $exception) {
            Log::error('Razorpay payment failed', [
                'order_id' => $order->id,
                'payment_id' => $paymentId,
                'error' => $exception->getMessage(),
            ]);

            $order->update([
                'status' => getConstant('ORDER_STATUS_FAILED'),
                'payment_error' => $exception->getMessage(),
            ]);

            return response()->json([
                'redirect_url' => route('ch_order_failed', [$order->encrypted_id]),
            ]);
        }
    }

    public function completePurchase(Order $order)
    {
        // Razorpay payments are captured during processPayment.
    }

    public function processIPN()
    {
        // Razorpay webhooks can be configured separately if needed.
    }

    public function getToken()
    {
        if (!$this->isConfigured()) {
            throw new \RuntimeException('Razorpay gateway is not configured.');
        }

        $currency = setting('currency', 'USD');
        $orderId = request()->query('order_id');
        $amount = 0.0;
        $receipt = $this->generateReceipt();

        if ($orderId) {
            $order = Order::where('id', $orderId)->firstOrFail();

            if ($order->user_id && auth()->id() && $order->user_id !== auth()->id()) {
                abort(403, 'Unauthorized order access.');
            }

            $amount = (float) $order->total;
            $currency = $order->currency ?? $currency;
            $receipt = $this->generateReceipt($order);
        } else {
            $amount = (float) $this->cartService->getTotal(false);
        }

        if ($amount <= 0) {
            throw new \RuntimeException('The order amount must be greater than zero for Razorpay payments.');
        }

        $amountInSubunits = (int) round($amount * 100);

        $orderData = $this->api->order->create([
            'receipt' => $receipt,
            'amount' => $amountInSubunits,
            'currency' => strtoupper($currency),
            'payment_capture' => 1,
            'notes' => array_filter([
                'order_id' => $orderId,
            ]),
        ]);

        return Arr::only($orderData->toArray(), ['id', 'amount', 'currency']);
    }

    public function isConfigured(): bool
    {
        return setting('razorpay.enabled') === 'yes'
            && !empty(setting('razorpay.key_id'))
            && !empty(setting('razorpay.key_secret'));
    }

    public function renderForm()
    {
        return [
            'id' => $this->getIdentifier(),
            'key' => setting('razorpay.key_id'),
            'name' => setting('company_name', config('app.name', 'ChargePanda')),
            'description' => setting('razorpay.description', 'Order Payment'),
            'theme_color' => setting('razorpay.theme_color', '#3399cc'),
            'order_id' => request()->route('order_id'),
        ];
    }

    public function getLabel(): string
    {
        return setting('razorpay.title', 'Razorpay');
    }

    public function getIdentifier(): string
    {
        return 'razorpay';
    }

    protected function verifySignature(string $orderId, string $paymentId, string $signature): void
    {
        $expectedSignature = hash_hmac('sha256', $orderId . '|' . $paymentId, (string) setting('razorpay.key_secret'));

        if (!hash_equals($expectedSignature, $signature)) {
            throw new \RuntimeException('Invalid Razorpay signature.');
        }
    }

    protected function generateReceipt(?Order $order = null): string
    {
        $base = $order
            ? 'order-' . ($order->order_number ?? $order->id)
            : 'cart-' . str_replace('-', '', (string) Str::uuid());

        return substr($base, 0, 40);
    }
}
