<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\UserPaymentMethod;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class StripePaymentMethodController extends Controller
{
    public function createSetupIntent(Request $request): JsonResponse
    {
        $user = $request->user();
        abort_unless($user && $user->role === 'client', 403);

        $secretKey = (string) config('lineup.payments.stripe.secret_key');
        if ($secretKey === '') {
            return response()->json([
                'message' => 'Stripe secret key manquante.',
            ], 422);
        }

        $data = $request->validate([
            'currency' => ['nullable', 'string', 'size:3'],
        ]);

        try {
            $customerId = $this->resolveOrCreateCustomerId(
                userId: $user->id,
                email: $user->email,
                name: $user->name,
                secretKey: $secretKey,
            );
        } catch (RuntimeException $exception) {
            return response()->json([
                'message' => $exception->getMessage(),
            ], 422);
        }

        $params = [
            'customer' => $customerId,
            'usage' => 'off_session',
            'payment_method_types[0]' => 'card',
            'metadata[user_id]' => (string) $user->id,
            'metadata[source]' => 'lineup_mobile_app',
        ];

        if (isset($data['currency']) && is_string($data['currency']) && $data['currency'] !== '') {
            $params['metadata[currency]'] = strtoupper($data['currency']);
        }

        $response = Http::timeout(20)
            ->asForm()
            ->withBasicAuth($secretKey, '')
            ->acceptJson()
            ->post('https://api.stripe.com/v1/setup_intents', $params);

        $payload = $response->json() ?: [];
        if (!$response->successful()) {
            return response()->json([
                'message' => $payload['error']['message'] ?? 'Impossible de creer le SetupIntent Stripe.',
                'payload' => is_array($payload) ? $payload : [],
            ], 422);
        }

        return response()->json([
            'data' => [
                'setup_intent_id' => $payload['id'] ?? null,
                'client_secret' => $payload['client_secret'] ?? null,
                'customer_id' => $customerId,
                'payload' => is_array($payload) ? $payload : [],
            ],
        ], 201);
    }

    public function store(Request $request): JsonResponse
    {
        $user = $request->user();
        abort_unless($user && $user->role === 'client', 403);

        $secretKey = (string) config('lineup.payments.stripe.secret_key');
        if ($secretKey === '') {
            return response()->json([
                'message' => 'Stripe secret key manquante.',
            ], 422);
        }

        $data = $request->validate([
            'payment_method_id' => ['required', 'string', 'max:255'],
            'customer_id' => ['nullable', 'string', 'max:255'],
            'label' => ['nullable', 'string', 'max:150'],
            'is_default' => ['nullable', 'boolean'],
        ]);

        $paymentMethodId = trim((string) $data['payment_method_id']);
        if ($paymentMethodId === '' || !str_starts_with($paymentMethodId, 'pm_')) {
            return response()->json([
                'message' => 'payment_method_id Stripe invalide. Attendu: pm_...',
            ], 422);
        }

        try {
            $customerId = isset($data['customer_id']) && is_string($data['customer_id']) && $data['customer_id'] !== ''
                ? $data['customer_id']
                : $this->resolveOrCreateCustomerId(
                    userId: $user->id,
                    email: $user->email,
                    name: $user->name,
                    secretKey: $secretKey,
                );

            $paymentMethod = $this->fetchPaymentMethod($paymentMethodId, $secretKey);
            $this->attachPaymentMethodIfNeeded($paymentMethod, $paymentMethodId, $customerId, $secretKey);
            $refreshedPaymentMethod = $this->fetchPaymentMethod($paymentMethodId, $secretKey);
        } catch (RuntimeException $exception) {
            return response()->json([
                'message' => $exception->getMessage(),
            ], 422);
        }

        $provider = $this->resolveProvider($refreshedPaymentMethod);
        $isDefault = (bool) ($data['is_default'] ?? true);
        $card = is_array($refreshedPaymentMethod['card'] ?? null) ? $refreshedPaymentMethod['card'] : [];
        $billingDetails = is_array($refreshedPaymentMethod['billing_details'] ?? null) ? $refreshedPaymentMethod['billing_details'] : [];

        $method = DB::transaction(function () use (
            $user,
            $paymentMethodId,
            $customerId,
            $provider,
            $card,
            $billingDetails,
            $refreshedPaymentMethod,
            $data,
            $isDefault
        ): UserPaymentMethod {
            if ($isDefault) {
                UserPaymentMethod::query()
                    ->where('user_id', $user->id)
                    ->where('type', UserPaymentMethod::TYPE_PAY_IN)
                    ->update(['is_default' => false]);
            }

            $existing = UserPaymentMethod::query()
                ->where('user_id', $user->id)
                ->where('type', UserPaymentMethod::TYPE_PAY_IN)
                ->whereIn('provider', ['stripe', 'apple_pay', 'google_pay'])
                ->get()
                ->first(function (UserPaymentMethod $method) use ($paymentMethodId): bool {
                    $details = is_array($method->details) ? $method->details : [];

                    return ($details['payment_method_id'] ?? null) === $paymentMethodId
                        || ($details['reference'] ?? null) === $paymentMethodId;
                });

            $brand = is_string($card['brand'] ?? null) ? (string) $card['brand'] : null;
            $last4 = is_string($card['last4'] ?? null) ? (string) $card['last4'] : null;
            $expMonth = isset($card['exp_month']) ? (int) $card['exp_month'] : null;
            $expYear = isset($card['exp_year']) ? (int) $card['exp_year'] : null;
            $funding = is_string($card['funding'] ?? null) ? (string) $card['funding'] : null;
            $walletType = is_string($card['wallet']['type'] ?? null) ? (string) $card['wallet']['type'] : null;

            $details = [
                'payment_method_id' => $paymentMethodId,
                'reference' => $paymentMethodId,
                'customer_id' => $customerId,
                'type' => $refreshedPaymentMethod['type'] ?? 'card',
                'brand' => $brand,
                'last4' => $last4,
                'exp_month' => $expMonth,
                'exp_year' => $expYear,
                'funding' => $funding,
                'wallet_type' => $walletType,
                'billing_name' => $billingDetails['name'] ?? $user->name,
                'billing_email' => $billingDetails['email'] ?? $user->email,
                'device_account' => $paymentMethodId,
            ];

            $label = $data['label'] ?? $this->defaultLabel($provider, $brand, $last4);

            $attributes = [
                'provider' => $provider,
                'label' => $label,
                'account_holder' => $billingDetails['name'] ?? $user->name,
                'details' => $details,
                'is_default' => $isDefault,
                'is_active' => true,
                'verified_at' => now(),
            ];

            if ($existing) {
                $existing->update($attributes);
                return $existing->fresh();
            }

            return UserPaymentMethod::create([
                'user_id' => $user->id,
                'type' => UserPaymentMethod::TYPE_PAY_IN,
                ...$attributes,
            ]);
        });

        return response()->json([
            'message' => 'Moyen de paiement Stripe ajoute.',
            'data' => [
                'payment_method' => $method,
                'stripe_payment_method_id' => $paymentMethodId,
                'stripe_customer_id' => $customerId,
            ],
        ], 201);
    }

    private function resolveOrCreateCustomerId(
        int|string $userId,
        ?string $email,
        ?string $name,
        string $secretKey,
    ): string {
        $existing = UserPaymentMethod::query()
            ->where('user_id', $userId)
            ->where('type', UserPaymentMethod::TYPE_PAY_IN)
            ->whereIn('provider', ['stripe', 'apple_pay', 'google_pay'])
            ->get()
            ->first(function (UserPaymentMethod $method): bool {
                $details = is_array($method->details) ? $method->details : [];
                $customerId = $details['customer_id'] ?? null;

                return is_string($customerId) && $customerId !== '';
            });

        if ($existing) {
            $details = is_array($existing->details) ? $existing->details : [];
            return (string) $details['customer_id'];
        }

        $response = Http::timeout(20)
            ->asForm()
            ->withBasicAuth($secretKey, '')
            ->acceptJson()
            ->post('https://api.stripe.com/v1/customers', [
                'email' => (string) ($email ?? ''),
                'name' => (string) ($name ?? ''),
                'metadata[user_id]' => (string) $userId,
                'metadata[source]' => 'lineup',
            ]);

        $payload = $response->json() ?: [];
        if (!$response->successful()) {
            throw new RuntimeException(
                $payload['error']['message'] ?? 'Impossible de creer le client Stripe.'
            );
        }

        $customerId = $payload['id'] ?? null;
        if (!is_string($customerId) || $customerId === '') {
            throw new RuntimeException('Customer Stripe manquant.');
        }

        return $customerId;
    }

    private function fetchPaymentMethod(string $paymentMethodId, string $secretKey): array
    {
        $response = Http::timeout(20)
            ->withBasicAuth($secretKey, '')
            ->acceptJson()
            ->get('https://api.stripe.com/v1/payment_methods/' . $paymentMethodId);

        $payload = $response->json() ?: [];
        if (!$response->successful()) {
            throw new RuntimeException(
                $payload['error']['message'] ?? 'Impossible de recuperer le PaymentMethod Stripe.'
            );
        }

        if (!is_array($payload)) {
            throw new RuntimeException('Reponse Stripe invalide pour le PaymentMethod.');
        }

        return $payload;
    }

    private function attachPaymentMethodIfNeeded(
        array $paymentMethod,
        string $paymentMethodId,
        string $customerId,
        string $secretKey,
    ): void {
        $attachedCustomer = $paymentMethod['customer'] ?? null;
        $attachedCustomer = is_string($attachedCustomer) && $attachedCustomer !== '' ? $attachedCustomer : null;

        if ($attachedCustomer !== null && $attachedCustomer !== $customerId) {
            throw new RuntimeException(
                'Le PaymentMethod Stripe est deja lie a un autre client. Reconnectez votre moyen de paiement.'
            );
        }

        if ($attachedCustomer === $customerId) {
            return;
        }

        $response = Http::timeout(20)
            ->asForm()
            ->withBasicAuth($secretKey, '')
            ->acceptJson()
            ->post('https://api.stripe.com/v1/payment_methods/' . $paymentMethodId . '/attach', [
                'customer' => $customerId,
            ]);

        $payload = $response->json() ?: [];
        if (!$response->successful()) {
            throw new RuntimeException(
                $payload['error']['message'] ?? 'Impossible de lier le PaymentMethod Stripe au client.'
            );
        }
    }

    private function resolveProvider(array $paymentMethod): string
    {
        $walletType = strtolower((string) ($paymentMethod['card']['wallet']['type'] ?? ''));

        return match ($walletType) {
            'apple_pay' => 'apple_pay',
            'google_pay' => 'google_pay',
            default => 'stripe',
        };
    }

    private function defaultLabel(string $provider, ?string $brand, ?string $last4): string
    {
        if ($provider === 'apple_pay') {
            return 'Apple Pay';
        }

        if ($provider === 'google_pay') {
            return 'Google Pay';
        }

        if ($brand !== null && $brand !== '' && $last4 !== null && $last4 !== '') {
            return strtoupper($brand) . ' •••• ' . $last4;
        }

        return 'Carte Stripe';
    }
}