<?php

namespace App\Services\Payment\Drivers;

use App\Models\Payment;
use App\Services\Payment\AbstractGatewayDriver;
use App\Services\Payment\PaymentResult;

/**
 * Stripe payment gateway driver.
 *
 * Uses the Stripe REST API directly via cURL -- no SDK required.
 * Creates a Checkout Session and redirects the customer to Stripe-hosted
 * payment page.  On completion, the callback / webhook verifies the
 * session and captures the payment intent ID.
 *
 * @see https://docs.stripe.com/api/checkout/sessions
 */
class StripeDriver extends AbstractGatewayDriver
{
    private const API_BASE = 'https://api.stripe.com/v1';

    private const MINIMUM_AMOUNTS = [
        'usd' => 50,   'eur' => 50,   'gbp' => 30,   'inr' => 5000,
        'aud' => 50,   'cad' => 50,   'sgd' => 50,   'hkd' => 400,
        'jpy' => 50,   'mxn' => 1000, 'brl' => 50,   'aed' => 200,
        'myr' => 200,  'nzd' => 50,   'thb' => 2000, 'chf' => 50,
        'dkk' => 250,  'nok' => 300,  'sek' => 300,
    ];

    public static function credentialFields(): array
    {
        return [
            ['key' => 'publishable_key', 'label' => 'Publishable Key', 'type' => 'text',     'required' => true],
            ['key' => 'secret_key',      'label' => 'Secret Key',      'type' => 'password',  'required' => true],
            ['key' => 'webhook_secret',  'label' => 'Webhook Secret',  'type' => 'password',  'required' => false,
             'hint' => 'Optional. Only needed if you set up webhooks in Stripe Dashboard.'],
        ];
    }

    public function initiate(Payment $payment, string $callbackUrl): array
    {
        $secretKey = $this->credential('secret_key');
        $currency  = strtolower($payment->currency ?? 'usd');
        $amount    = $this->amountInSmallestUnit($payment);
        $minimum   = self::MINIMUM_AMOUNTS[$currency] ?? 50;

        if ($amount < $minimum) {
            $humanMin = number_format($minimum / 100, 2);
            throw new \RuntimeException(
                "The payment amount is below Stripe's minimum chargeable amount for " .
                strtoupper($currency) . " (minimum: {$humanMin} " . strtoupper($currency) . ")."
            );
        }

        $sessionData = [
            'payment_method_types[]' => 'card',
            'mode'                   => 'payment',
            'success_url'            => $callbackUrl . (str_contains($callbackUrl, '?') ? '&' : '?') . 'session_id={CHECKOUT_SESSION_ID}',
            'cancel_url'             => $callbackUrl . (str_contains($callbackUrl, '?') ? '&' : '?') . 'cancelled=1',
            'client_reference_id'    => $payment->id,
            'line_items[0][price_data][currency]'     => $currency,
            'line_items[0][price_data][unit_amount]'   => $amount,
            'line_items[0][price_data][product_data][name]' => $this->paymentDescription($payment),
            'line_items[0][quantity]' => 1,
            'metadata[payment_id]'   => $payment->id,
        ];

        $response = $this->stripeRequest('POST', '/checkout/sessions', $sessionData, $secretKey);

        if (isset($response['json']['url'])) {
            return ['redirect_url' => $response['json']['url']];
        }

        $errorMsg = $response['json']['error']['message'] ?? 'Failed to create Stripe checkout session.';
        throw new \RuntimeException("Stripe: {$errorMsg}");
    }

    /**
     * Mobile order creation — used by /api/v1/mobile/payments/initiate.
     *
     * Creates a Stripe PaymentIntent server-side and returns its
     * client_secret. The Flutter Stripe SDK ("Payment Sheet") then opens
     * the native UI with `clientSecret` + publishable key, completes the
     * charge, and calls back the app on success. The app posts the
     * resulting payment_intent_id to /payments/verify, which we then
     * confirm against Stripe's API server-side using the secret key.
     *
     * @see https://stripe.com/docs/payments/accept-a-payment?platform=mobile
     */
    public function createMobileOrder(float $amount, string $currency, ?string $purpose = null, ?int $workspaceId = null): array
    {
        $secretKey = $this->credential('secret_key');
        $publishable = $this->credential('publishable_key');

        if (!$secretKey || !$publishable) {
            throw new \RuntimeException('Stripe credentials are not configured.');
        }

        $currencyLower = strtolower($currency ?: 'usd');
        $amountSmallest = (int) round($amount * 100);

        $minimum = self::MINIMUM_AMOUNTS[$currencyLower] ?? 50;
        if ($amountSmallest < $minimum) {
            $humanMin = number_format($minimum / 100, 2);
            throw new \RuntimeException(
                "Amount is below Stripe's minimum for " . strtoupper($currencyLower)
                . " (minimum: {$humanMin} " . strtoupper($currencyLower) . ")."
            );
        }

        $payload = [
            'amount'                      => $amountSmallest,
            'currency'                    => $currencyLower,
            'description'                 => $purpose ?: 'Subscription payment',
            'automatic_payment_methods[enabled]' => 'true',
            'metadata[source]'            => 'mobile_app',
            'metadata[purpose]'           => $purpose ?: 'subscription',
            'metadata[workspace_id]'      => (string) $workspaceId,
        ];

        $response = $this->stripeRequest('POST', '/payment_intents', $payload, $secretKey);
        $json = $response['json'] ?? [];

        if (!isset($json['id'], $json['client_secret'])) {
            $errorMsg = $json['error']['message'] ?? 'Failed to create Stripe PaymentIntent.';
            throw new \RuntimeException("Stripe: {$errorMsg}");
        }

        return [
            // Pass `client_secret` into Stripe's mobile SDK (Payment Sheet).
            // The intent id is what we'll verify against on the verify call.
            'gateway_order_id' => $json['id'],
            'client_secret'    => $json['client_secret'],
            'publishable_key'  => $publishable,
            'amount'           => $amountSmallest,
            'amount_decimal'   => (float) $amount,
            'currency'         => strtoupper($currencyLower),
            'status'           => $json['status'] ?? 'requires_payment_method',
            'merchant_name'    => config('app.name'),
            'description'      => $purpose ?: 'Subscription payment',
        ];
    }

    /**
     * Verify a Stripe payment after the mobile SDK reports success.
     *
     * Unlike Razorpay (which signs the response with HMAC the client
     * can replay), Stripe doesn't return a signed payload to the app.
     * Instead we re-fetch the PaymentIntent server-side and check its
     * status is `succeeded`. The `signature` arg is accepted for API
     * symmetry with verifyMobilePayment() across drivers, but ignored
     * here — Stripe's source of truth IS the server-side status.
     */
    public function verifyMobilePayment(string $stripeIntentId, string $stripePaymentMethodId, string $signature): bool
    {
        $secretKey = $this->credential('secret_key');
        if (!$secretKey) {
            return false;
        }

        $response = $this->stripeRequest('GET', "/payment_intents/{$stripeIntentId}", [], $secretKey);
        $json = $response['json'] ?? [];

        // Source of truth: the intent must be in the terminal "succeeded"
        // status. Anything else (requires_action, processing, requires_
        // payment_method) means the customer didn't actually complete the
        // charge yet — the SDK can lie or be tampered with on the client.
        return ($json['status'] ?? '') === 'succeeded';
    }

    public function handleCallback(array $payload): PaymentResult
    {
        $sessionId = $payload['session_id'] ?? null;

        if (isset($payload['cancelled'])) {
            return PaymentResult::failure('Payment was cancelled by the customer.');
        }

        if (! $sessionId) {
            return PaymentResult::failure('Missing Stripe session ID in callback.');
        }

        $secretKey = $this->credential('secret_key');
        $response  = $this->stripeRequest('GET', "/checkout/sessions/{$sessionId}", [], $secretKey);
        $session   = $response['json'] ?? [];

        if (($session['payment_status'] ?? '') === 'paid') {
            return PaymentResult::success(
                transactionId: $session['payment_intent'] ?? $sessionId,
                message: 'Payment completed successfully.',
                metadata: [
                    'payment_id'               => $session['metadata']['payment_id'] ?? $session['client_reference_id'] ?? null,
                    'stripe_session_id'        => $sessionId,
                    'stripe_payment_intent'    => $session['payment_intent'] ?? null,
                    'stripe_customer'          => $session['customer'] ?? null,
                ],
            );
        }

        return PaymentResult::failure(
            'Payment not completed. Status: ' . ($session['payment_status'] ?? 'unknown'),
        );
    }

    public function verifyWebhookSignature(string $rawBody, array $headers): bool
    {
        $webhookSecret = $this->credential('webhook_secret');
        if (! $webhookSecret) {
            \Illuminate\Support\Facades\Log::warning('Stripe webhook secret not configured -- skipping signature verification.');
            return true;
        }

        $sigHeader = $headers['stripe-signature'] ?? $headers['Stripe-Signature'] ?? '';
        if (! $sigHeader) {
            return false;
        }

        $parts = [];
        foreach (explode(',', $sigHeader) as $item) {
            [$key, $value] = explode('=', $item, 2) + [1 => ''];
            $parts[$key] = $value;
        }

        $timestamp = $parts['t'] ?? '';
        $signature = $parts['v1'] ?? '';

        if (! $timestamp || ! $signature) {
            return false;
        }

        if (abs(time() - (int) $timestamp) > 300) {
            return false;
        }

        $signedPayload = $timestamp . '.' . $rawBody;
        $expected = hash_hmac('sha256', $signedPayload, $webhookSecret);

        return hash_equals($expected, $signature);
    }

    public function handleWebhook(array $payload): PaymentResult
    {
        $type = $payload['type'] ?? '';
        $data = $payload['data']['object'] ?? [];

        if ($type === 'checkout.session.completed' && ($data['payment_status'] ?? '') === 'paid') {
            return PaymentResult::success(
                transactionId: $data['payment_intent'] ?? $data['id'],
                message: 'Webhook confirmed payment.',
                metadata: [
                    'payment_id'            => $data['metadata']['payment_id'] ?? $data['client_reference_id'] ?? null,
                    'stripe_event_type'     => $type,
                    'stripe_session_id'     => $data['id'] ?? null,
                    'stripe_payment_intent' => $data['payment_intent'] ?? null,
                ],
            );
        }

        if ($type === 'payment_intent.succeeded') {
            return PaymentResult::success(
                transactionId: $data['id'],
                message: 'Payment intent succeeded.',
                metadata: ['stripe_event_type' => $type],
            );
        }

        return PaymentResult::failure("Unhandled Stripe webhook event: {$type}");
    }

    public function verify(Payment $payment): PaymentResult
    {
        $transactionId = $payment->gateway_transaction_id ?? null;
        if (! $transactionId) {
            return PaymentResult::failure('No transaction ID available for verification.');
        }

        $secretKey = $this->credential('secret_key');
        $response = $this->stripeRequest('GET', "/payment_intents/{$transactionId}", [], $secretKey);
        $intent   = $response['json'] ?? [];

        if (($intent['status'] ?? '') === 'succeeded') {
            return PaymentResult::success(
                transactionId: $intent['id'],
                message: 'Payment verified successfully.',
                metadata: [
                    'amount_received' => $intent['amount_received'] ?? null,
                    'currency'        => $intent['currency'] ?? null,
                ],
            );
        }

        return PaymentResult::failure('Payment not verified. Status: ' . ($intent['status'] ?? 'unknown'));
    }

    private function stripeRequest(string $method, string $path, array $data, string $secretKey): array
    {
        $url = self::API_BASE . $path;
        $ch  = curl_init();

        $headers = [
            'Authorization: Basic ' . base64_encode($secretKey . ':'),
            'Accept: application/json',
        ];

        curl_setopt_array($ch, [
            CURLOPT_URL            => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 30,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
        ]);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
        } elseif ($method === 'GET' && $data) {
            curl_setopt($ch, CURLOPT_URL, $url . '?' . http_build_query($data));
        }

        $body   = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error  = curl_error($ch);
        curl_close($ch);

        if ($body === false) {
            throw new \RuntimeException("Stripe cURL error: {$error}");
        }

        return [
            'status' => $status,
            'body'   => $body,
            'json'   => json_decode($body, true),
        ];
    }
}
