<?php

namespace App\Services\Payments\Gateways;

use App\Models\UserPaymentMethod;
use App\Services\Payments\Contracts\PaymentGateway;
use App\Services\Payments\GatewayResult;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class PayPalPaymentGateway implements PaymentGateway
{
    public function supports(string $provider): bool
    {
        return $provider === 'paypal';
    }

    public function charge(
        UserPaymentMethod $method,
        float $amount,
        string $currency,
        array $metadata = [],
    ): GatewayResult {
        $details = is_array($method->details) ? $method->details : [];
        $vaultToken = $details['vault_id'] ?? $details['vault_token'] ?? $details['reference'] ?? null;

        if (!is_string($vaultToken) || $vaultToken === '') {
            return new GatewayResult(
                status: GatewayResult::STATUS_FAILED,
                message: 'Token PayPal vault manquant pour debiter automatiquement.',
            );
        }

        try {
            $token = $this->accessToken();
        } catch (RuntimeException $exception) {
            return new GatewayResult(
                status: GatewayResult::STATUS_FAILED,
                message: $exception->getMessage(),
            );
        }

        $isMerchantSubsequent = ($metadata['charge_mode'] ?? null) === 'merchant_subsequent';
        $usagePattern = (string) ($details['usage_pattern'] ?? 'UNSCHEDULED_POSTPAID');

        $orderPayload = [
            'intent' => 'CAPTURE',
            'purchase_units' => [[
                'amount' => [
                    'currency_code' => strtoupper($currency),
                    'value' => number_format($amount, 2, '.', ''),
                ],
            ]],
            'application_context' => [
                'shipping_preference' => 'NO_SHIPPING',
            ],
        ];

        $tokenType = strtoupper((string) ($details['vault_token_type'] ?? ''));
        $isLegacyBillingAgreementToken = $tokenType === 'BILLING_AGREEMENT';

        if (!$isLegacyBillingAgreementToken) {
            $orderPayload['payment_source'] = [
                'paypal' => [
                    'vault_id' => $vaultToken,
                    'stored_credential' => [
                        'payment_initiator' => (string) ($metadata['payment_initiator'] ?? 'MERCHANT'),
                        'payment_type' => (string) ($metadata['payment_type'] ?? 'ONE_TIME'),
                        'usage' => (string) ($metadata['stored_credential_usage'] ?? 'SUBSEQUENT'),
                    ],
                ],
            ];

            if ($isMerchantSubsequent) {
                $orderPayload['payment_source']['paypal']['attributes'] = [
                    'vault' => [
                        'usage_pattern' => $usagePattern,
                    ],
                ];
            }
        } else {
            $orderPayload['payment_source'] = [
                'token' => [
                    'id' => $vaultToken,
                    'type' => (string) ($details['vault_token_type'] ?? 'BILLING_AGREEMENT'),
                ],
            ];
        }

        $createOrderResponse = Http::timeout(20)
            ->withToken($token)
            ->acceptJson()
            ->post($this->baseUrl() . '/v2/checkout/orders', $orderPayload);

        $orderResponsePayload = $createOrderResponse->json() ?: [];
        if (!$createOrderResponse->successful()) {
            return new GatewayResult(
                status: GatewayResult::STATUS_FAILED,
                message: $this->paypalMessage($orderResponsePayload, 'La creation de la commande PayPal a echoue.'),
                payload: is_array($orderResponsePayload) ? $orderResponsePayload : [],
            );
        }

        $orderId = $orderResponsePayload['id'] ?? null;
        if (!is_string($orderId) || $orderId === '') {
            return new GatewayResult(
                status: GatewayResult::STATUS_FAILED,
                message: 'PayPal order id manquant.',
                payload: is_array($orderResponsePayload) ? $orderResponsePayload : [],
            );
        }

        $captureResponse = Http::timeout(20)
            ->withToken($token)
            ->acceptJson()
            ->post($this->baseUrl() . '/v2/checkout/orders/' . $orderId . '/capture');

        $capturePayload = $captureResponse->json() ?: [];
        if (!$captureResponse->successful()) {
            return new GatewayResult(
                status: GatewayResult::STATUS_FAILED,
                providerReference: $orderId,
                message: $this->paypalMessage($capturePayload, 'La capture PayPal a echoue.'),
                payload: is_array($capturePayload) ? $capturePayload : [],
            );
        }

        $status = strtoupper((string) ($capturePayload['status'] ?? ''));
        if ($status === 'COMPLETED') {
            return new GatewayResult(
                status: GatewayResult::STATUS_PAID,
                providerReference: $this->paypalCaptureId($capturePayload) ?? $orderId,
                payload: is_array($capturePayload) ? $capturePayload : [],
            );
        }

        if (in_array($status, ['PENDING', 'CREATED'], true)) {
            return new GatewayResult(
                status: GatewayResult::STATUS_PENDING,
                providerReference: $orderId,
                payload: is_array($capturePayload) ? $capturePayload : [],
            );
        }

        return new GatewayResult(
            status: GatewayResult::STATUS_FAILED,
            providerReference: $orderId,
            message: $this->paypalMessage($capturePayload, 'PayPal capture non complete.'),
            payload: is_array($capturePayload) ? $capturePayload : [],
        );
    }

    public function payout(
        UserPaymentMethod $method,
        float $amount,
        string $currency,
        array $metadata = [],
    ): GatewayResult {
        $details = is_array($method->details) ? $method->details : [];
        $receiver = $details['email'] ?? $details['reference'] ?? null;

        if (!is_string($receiver) || $receiver === '') {
            return new GatewayResult(
                status: GatewayResult::STATUS_FAILED,
                message: 'Email PayPal destinataire manquant.',
            );
        }

        try {
            $token = $this->accessToken();
        } catch (RuntimeException $exception) {
            return new GatewayResult(
                status: GatewayResult::STATUS_FAILED,
                message: $exception->getMessage(),
            );
        }

        $batchId = 'lineup-' . now()->format('YmdHis') . '-' . random_int(1000, 9999);

        $response = Http::timeout(20)
            ->withToken($token)
            ->acceptJson()
            ->post($this->baseUrl() . '/v1/payments/payouts', [
                'sender_batch_header' => [
                    'sender_batch_id' => $batchId,
                    'email_subject' => 'Retrait LineUp',
                ],
                'items' => [[
                    'recipient_type' => 'EMAIL',
                    'amount' => [
                        'value' => number_format($amount, 2, '.', ''),
                        'currency' => strtoupper($currency),
                    ],
                    'receiver' => $receiver,
                    'note' => 'Virement de retrait LineUp',
                ]],
            ]);

        $payload = $response->json() ?: [];
        if (!$response->successful()) {
            return new GatewayResult(
                status: GatewayResult::STATUS_FAILED,
                message: $this->paypalMessage($payload, 'Le retrait PayPal a echoue.'),
                payload: is_array($payload) ? $payload : [],
            );
        }

        return new GatewayResult(
            status: GatewayResult::STATUS_PENDING,
            providerReference: $payload['batch_header']['payout_batch_id'] ?? $batchId,
            payload: is_array($payload) ? $payload : [],
        );
    }

    public function verifyWebhookSignature(array $headers, array $eventPayload): bool
    {
        $webhookId = (string) config('lineup.payments.paypal.webhook_id');
        if ($webhookId === '') {
            return true;
        }

        try {
            $token = $this->accessToken();
        } catch (RuntimeException) {
            return false;
        }

        $response = Http::timeout(20)
            ->withToken($token)
            ->acceptJson()
            ->post($this->baseUrl() . '/v1/notifications/verify-webhook-signature', [
                'transmission_id' => $headers['paypal-transmission-id'] ?? '',
                'transmission_time' => $headers['paypal-transmission-time'] ?? '',
                'cert_url' => $headers['paypal-cert-url'] ?? '',
                'auth_algo' => $headers['paypal-auth-algo'] ?? '',
                'transmission_sig' => $headers['paypal-transmission-sig'] ?? '',
                'webhook_id' => $webhookId,
                'webhook_event' => $eventPayload,
            ]);

        if (!$response->successful()) {
            return false;
        }

        return strtoupper((string) $response->json('verification_status')) === 'SUCCESS';
    }

   public function createVaultSetupOrder(
    float $amount = 0,
    string $currency = 'EUR',
    array $context = [],
): array {
    $returnUrl = (string) ($context['return_url'] ?? 'https://lineupfrance.com/paypal/return');
    $cancelUrl = (string) ($context['cancel_url'] ?? 'https://lineupfrance.com/paypal/cancel');

    if (config('lineup.payments.simulate', true)) {
        $setupToken = 'sim_setup_' . now()->format('YmdHis') . '_' . random_int(1000, 9999);

        return [
            'setup_token_id' => $setupToken,
            'order_id' => $setupToken,
            'approve_url' => 'https://example.test/paypal/approve/' . $setupToken,
            'status' => 'PAYER_ACTION_REQUIRED',
            'payload' => [
                'simulated' => true,
            ],
        ];
    }

    $token = $this->accessToken();

    $payload = [
        'payment_source' => [
            'paypal' => [
                'usage_type' => 'MERCHANT',
                'experience_context' => [
                    'return_url' => $returnUrl,
                    'cancel_url' => $cancelUrl,
                ],
            ],
        ],
    ];

    $paypalRequestId = 'vault-setup-' . now()->format('YmdHis') . '-' . random_int(1000, 9999);

    $response = Http::timeout(20)
        ->withToken($token)
        ->withHeaders([
            'PayPal-Request-Id' => $paypalRequestId,
            'Content-Type' => 'application/json',
        ])
        ->acceptJson()
        ->post($this->baseUrl() . '/v3/vault/setup-tokens', $payload);

    $responsePayload = $response->json() ?: [];

    if (!$response->successful()) {
        $details = is_array($responsePayload) ? json_encode($responsePayload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : '';
        throw new RuntimeException(
            $this->paypalMessage($responsePayload, 'La creation du setup token PayPal a echoue.') .
            ($details ? ' Payload PayPal: ' . $details : '')
        );
    }

    $setupTokenId = $responsePayload['id'] ?? null;
    if (!is_string($setupTokenId) || $setupTokenId === '') {
        throw new RuntimeException('Setup token PayPal manquant.');
    }

    $approveUrl = $this->extractApproveUrl($responsePayload);

    return [
        'setup_token_id' => $setupTokenId,
        'order_id' => $setupTokenId,
        'approve_url' => $approveUrl,
        'status' => (string) ($responsePayload['status'] ?? ''),
        'payload' => is_array($responsePayload) ? $responsePayload : [],
    ];
}
    public function captureVaultSetupOrder(string $orderId): array
    {
        if ($orderId === '') {
            throw new RuntimeException('Setup token PayPal manquant.');
        }

        if (config('lineup.payments.simulate', true)) {
            $vaultId = 'sim_vault_' . now()->format('YmdHis') . '_' . random_int(1000, 9999);

            return [
                'setup_token_id' => $orderId,
                'order_id' => $orderId,
                'capture_id' => null,
                'vault_id' => $vaultId,
                'vault_status' => 'APPROVED',
                'payer_id' => 'sim_payer_' . random_int(1000, 9999),
                'payer_email' => null,
                'payload' => [
                    'simulated' => true,
                ],
            ];
        }

        $token = $this->accessToken();

        $paypalRequestId = 'vault-confirm-' . now()->format('YmdHis') . '-' . random_int(1000, 9999);

        $response = Http::timeout(20)
            ->withToken($token)
            ->withHeaders([
                'PayPal-Request-Id' => $paypalRequestId,
            ])
            ->acceptJson()
            ->post($this->baseUrl() . '/v3/vault/payment-tokens', [
                'payment_source' => [
                    'token' => [
                        'id' => $orderId,
                        'type' => 'SETUP_TOKEN',
                    ],
                ],
            ]);

        $payload = $response->json() ?: [];
        if (!$response->successful()) {
            throw new RuntimeException($this->paypalMessage($payload, 'La confirmation du setup token PayPal a echoue.'));
        }

        return [
            'setup_token_id' => $orderId,
            'order_id' => $orderId,
            'capture_id' => null,
            'vault_id' => $payload['id'] ?? null,
            'vault_status' => (string) ($payload['status'] ?? ''),
            'payer_id' => $payload['customer']['payer_id'] ?? $payload['customer']['id'] ?? null,
            'payer_email' => $payload['customer']['email_address'] ?? null,
            'payload' => is_array($payload) ? $payload : [],
        ];
    }

    private function accessToken(): string
    {
        $clientId = trim((string) config('lineup.payments.paypal.client_id'));
        $clientSecret = trim((string) config('lineup.payments.paypal.client_secret'));
        $mode = (string) config('lineup.payments.paypal.mode', 'sandbox');

        if ($clientId === '' || $clientSecret === '') {
            throw new RuntimeException('Identifiants PayPal manquants.');
        }

        $response = Http::timeout(15)
            ->asForm()
            ->withBasicAuth($clientId, $clientSecret)
            ->acceptJson()
            ->post($this->baseUrl() . '/v1/oauth2/token', [
                'grant_type' => 'client_credentials',
            ]);

        $payload = $response->json();

        if (!$response->successful()) {
            $message = 'Echec generation token PayPal.';
            if (is_array($payload)) {
                $paypalError = $payload['error_description']
                    ?? $payload['error']
                    ?? $payload['message']
                    ?? null;

                if (is_string($paypalError) && $paypalError !== '') {
                    $message .= ' ' . $paypalError;
                }
            }

            $message .= ' [HTTP ' . $response->status() . ', mode=' . $mode . ']';

            throw new RuntimeException($message);
        }

        $token = (string) ($payload['access_token'] ?? '');
        if ($token === '') {
            throw new RuntimeException('Token PayPal vide.');
        }

        return $token;
    }

    private function baseUrl(): string
    {
        return config('lineup.payments.paypal.mode') === 'live'
            ? 'https://api-m.paypal.com'
            : 'https://api-m.sandbox.paypal.com';
    }

    private function paypalMessage(array $payload, string $fallback): string
    {
        if (isset($payload['message']) && is_string($payload['message']) && $payload['message'] !== '') {
            return $payload['message'];
        }

        if (isset($payload['details'][0]['description']) && is_string($payload['details'][0]['description'])) {
            return $payload['details'][0]['description'];
        }

        if (isset($payload['error_description']) && is_string($payload['error_description']) && $payload['error_description'] !== '') {
            return $payload['error_description'];
        }

        if (isset($payload['error']) && is_string($payload['error']) && $payload['error'] !== '') {
            return $payload['error'];
        }

        return $fallback;
    }

    private function paypalCaptureId(array $payload): ?string
    {
        $capture = $payload['purchase_units'][0]['payments']['captures'][0]['id'] ?? null;
        return is_string($capture) && $capture !== '' ? $capture : null;
    }

    private function extractApproveUrl(array $payload): ?string
    {
        $links = $payload['links'] ?? [];
        if (!is_array($links)) {
            return null;
        }

        foreach ($links as $link) {
            if (!is_array($link)) {
                continue;
            }

            $rel = strtolower((string) ($link['rel'] ?? ''));
            $href = $link['href'] ?? null;
            if (in_array($rel, ['approve', 'payer-action'], true) && is_string($href) && $href !== '') {
                return $href;
            }
        }

        return null;
    }

    private function extractVaultId(array $payload): ?string
    {
        $candidates = [
            $payload['payment_source']['paypal']['attributes']['vault']['id'] ?? null,
            $payload['payment_source']['paypal']['vault']['id'] ?? null,
            $payload['purchase_units'][0]['payments']['captures'][0]['payment_source']['paypal']['attributes']['vault']['id'] ?? null,
            $payload['purchase_units'][0]['payments']['captures'][0]['payment_source']['paypal']['vault']['id'] ?? null,
            $payload['id'] ?? null,
        ];

        foreach ($candidates as $candidate) {
            if (is_string($candidate) && $candidate !== '') {
                return $candidate;
            }
        }

        return null;
    }

    private function extractVaultStatus(array $payload): ?string
    {
        $candidates = [
            $payload['payment_source']['paypal']['attributes']['vault']['status'] ?? null,
            $payload['payment_source']['paypal']['vault']['status'] ?? null,
            $payload['purchase_units'][0]['payments']['captures'][0]['payment_source']['paypal']['attributes']['vault']['status'] ?? null,
            $payload['purchase_units'][0]['payments']['captures'][0]['payment_source']['paypal']['vault']['status'] ?? null,
            $payload['status'] ?? null,
        ];

        foreach ($candidates as $candidate) {
            if (is_string($candidate) && $candidate !== '') {
                return $candidate;
            }
        }

        return null;
    }

    private function extractPayerId(array $payload): ?string
    {
        $candidates = [
            $payload['payer']['payer_id'] ?? null,
            $payload['customer']['payer_id'] ?? null,
            $payload['customer']['id'] ?? null,
        ];

        foreach ($candidates as $candidate) {
            if (is_string($candidate) && $candidate !== '') {
                return $candidate;
            }
        }

        return null;
    }

    private function extractPayerEmail(array $payload): ?string
    {
        $candidates = [
            $payload['payer']['email_address'] ?? null,
            $payload['customer']['email_address'] ?? null,
        ];

        foreach ($candidates as $candidate) {
            if (is_string($candidate) && $candidate !== '') {
                return $candidate;
            }
        }

        return null;
    }
}
