<?php

namespace App\Services;

use App\Exceptions\PaymentGatewayException;
use Razorpay\Api\Api;
use App\Models\Setting;
use Illuminate\Support\Facades\Log;

class RazorpayService
{
    public const PAYMENT_STATUS_AUTHORIZED = 'authorized';
    public const PAYMENT_STATUS_CAPTURED = 'captured';
    public const PAYMENT_STATUS_FAILED = 'failed';
    public const PAYMENT_STATUS_UNKNOWN = 'unknown';

    protected Api $api;

    public function __construct()
    {
        // Try to get from Settings table first, fallback to config/env for backward compatibility
        $key = Setting::get('payment_razorpay_key_id');
        $secret = Setting::get('payment_razorpay_secret_key');
        
        // Fallback to config/env if not in database
        if (empty($key)) {
            $key = config('services.razorpay.key');
        }
        if (empty($secret)) {
            $secret = config('services.razorpay.secret');
        }

        if (empty($key) || empty($secret)) {
            Log::error('Razorpay credentials not configured', [
                'key_exists' => !empty($key),
                'secret_exists' => !empty($secret),
                'source' => 'Settings table or .env file',
            ]);

            $message = 'Razorpay credentials are not configured. ';
            $message .= 'Please configure them in Admin > Settings > Payment Settings > Razorpay API Keys, ';
            $message .= 'or set in your .env file: RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET';

            throw new \Exception($message);
        }

        $this->api = new Api($key, $secret);
    }

    public function createOrder(float $amount, string $receipt): array
    {
        try {
            // Amount should be in paise
            $amountInPaise = (int) ($amount * 100);

            $order = $this->api->order->create([
                'receipt' => $receipt,
                'amount' => $amountInPaise,
                'currency' => 'INR',
                // Authorize-only flow: capture is performed server-side after stock confirmation.
                'payment_capture' => 0,
            ]);

            return [
                'id' => $order->id,
                'amount' => $order->amount,
                'currency' => $order->currency,
                'receipt' => $order->receipt,
            ];
        } catch (\Razorpay\Api\Errors\BadRequestError $e) {
            Log::error('Razorpay BadRequestError', [
                'amount' => $amount,
                'receipt' => $receipt,
                'error' => $e->getMessage(),
                // Some SDK versions don't expose getDescription/getField consistently; keep raw message for reliability.
                'code' => method_exists($e, 'getCode') ? $e->getCode() : null,
            ]);
            throw new \Exception('Razorpay API Error: ' . $e->getMessage());
        } catch (\Exception $e) {
            // Covers AuthenticationError and any other Razorpay SDK exceptions.
            if (str_contains(strtolower($e->getMessage()), 'authentication')) {
                Log::error('Razorpay Authentication Error', [
                    'amount' => $amount,
                    'receipt' => $receipt,
                    'error' => $e->getMessage(),
                ]);
                throw new \Exception('Razorpay Authentication Failed. Please check your RZP_KEY and RZP_SECRET in .env file.');
            }

            Log::error('Razorpay order creation failed', [
                'amount' => $amount,
                'receipt' => $receipt,
                'error' => $e->getMessage(),
                'error_class' => get_class($e),
            ]);

            throw new \Exception('Failed to create Razorpay order: ' . $e->getMessage());
        }
    }

    public function getOrderDetails(string $razorpayOrderId): ?array
    {
        try {
            $order = $this->api->order->fetch($razorpayOrderId);
            return $order->toArray();
        } catch (\Exception $e) {
            Log::error('Razorpay order fetch failed', [
                'razorpay_order_id' => $razorpayOrderId,
                'error' => $e->getMessage(),
            ]);

            return null;
        }
    }

    public function verifySignature(string $orderId, string $paymentId, string $signature): bool
    {
        try {
            $attributes = [
                'razorpay_order_id' => $orderId,
                'razorpay_payment_id' => $paymentId,
                'razorpay_signature' => $signature,
            ];

            $this->api->utility->verifyPaymentSignature($attributes);

            return true;
        } catch (\Exception $e) {
            Log::error('Razorpay signature verification failed', [
                'order_id' => $orderId,
                'payment_id' => $paymentId,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    public function getPaymentDetails(string $paymentId): ?array
    {
        try {
            $payment = $this->api->payment->fetch($paymentId);

            return $payment->toArray();
        } catch (\Exception $e) {
            Log::error('Razorpay payment fetch failed', [
                'payment_id' => $paymentId,
                'error' => $e->getMessage(),
            ]);

            return null;
        }
    }

    /**
     * Resolve canonical payment status for shared verify/webhook flows.
     *
     * @return array{status:string, raw:array<string,mixed>|null}
     */
    public function resolvePaymentState(string $paymentId): array
    {
        $details = $this->getPaymentDetails($paymentId);
        if ($details === null) {
            return ['status' => self::PAYMENT_STATUS_UNKNOWN, 'raw' => null];
        }

        return [
            'status' => $this->normalizePaymentStatus($details['status'] ?? null),
            'raw' => $details,
        ];
    }

    public function normalizePaymentStatus(?string $status): string
    {
        return match (strtolower((string) $status)) {
            self::PAYMENT_STATUS_AUTHORIZED => self::PAYMENT_STATUS_AUTHORIZED,
            self::PAYMENT_STATUS_CAPTURED => self::PAYMENT_STATUS_CAPTURED,
            self::PAYMENT_STATUS_FAILED => self::PAYMENT_STATUS_FAILED,
            default => self::PAYMENT_STATUS_UNKNOWN,
        };
    }

    /**
     * Capture an authorized payment.
     *
     * @throws \Exception
     */
    public function capturePayment(string $paymentId, float $amount): array
    {
        try {
            $amountInPaise = (int) round($amount * 100);
            $payment = $this->api->payment->fetch($paymentId);
            $captured = $payment->capture(['amount' => $amountInPaise]);

            return $captured->toArray();
        } catch (\Exception $e) {
            $reason = $this->classifyCaptureErrorReason($e);
            Log::error('Razorpay payment capture failed', [
                'payment_id' => $paymentId,
                'amount' => $amount,
                'error' => $e->getMessage(),
                'reason_code' => $reason,
            ]);

            throw new PaymentGatewayException(
                'Failed to capture payment: ' . $e->getMessage(),
                $reason,
                (int) $e->getCode(),
                $e
            );
        }
    }

    private function classifyCaptureErrorReason(\Exception $e): string
    {
        $message = strtolower($e->getMessage());

        if (str_contains($message, 'already captured')) {
            return PaymentGatewayException::CODE_CAPTURE_ALREADY_CAPTURED;
        }

        if (
            str_contains($message, 'timeout')
            || str_contains($message, 'timed out')
            || str_contains($message, 'temporar')
            || str_contains($message, 'try again')
        ) {
            return PaymentGatewayException::CODE_CAPTURE_TRANSIENT;
        }

        return PaymentGatewayException::CODE_CAPTURE_FAILED;
    }
}
