<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\UserPaymentMethod;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class PaymentMethodController extends Controller
{
    private const PAY_IN_PROVIDERS = [
        'paypal',
        'stripe',
        'apple_pay',
        'google_pay',
    ];

    private const PAY_OUT_PROVIDERS = [
        'rib',
        'paypal',
        'stripe',
        'apple_pay',
        'google_pay',
    ];

    public function index(Request $request): JsonResponse
    {
        $user = $request->user();
        $forcedType = $this->forcedTypeForRole($user);

        $request->validate([
            'type' => ['nullable', Rule::in([UserPaymentMethod::TYPE_PAY_IN, UserPaymentMethod::TYPE_PAY_OUT])],
        ]);

        $type = $forcedType ?? trim($request->string('type')->toString());

        $query = UserPaymentMethod::query()
            ->where('user_id', $user->id)
            ->where('is_active', true)
            ->orderByDesc('is_default')
            ->latest();

        if ($type !== '') {
            $query->where('type', $type);
        }

        return response()->json([
            'data' => $query->get(),
        ]);
    }

    public function store(Request $request): JsonResponse
    {
        $user = $request->user();
        $forcedType = $this->forcedTypeForRole($user);

        $data = $request->validate([
            'type' => ['nullable', Rule::in([UserPaymentMethod::TYPE_PAY_IN, UserPaymentMethod::TYPE_PAY_OUT])],
            'provider' => ['required', 'string', 'max:50'],
            'label' => ['nullable', 'string', 'max:150'],
            'account_holder' => ['nullable', 'string', 'max:255'],
            'details' => ['nullable', 'array'],
            'is_default' => ['nullable', 'boolean'],
            'is_active' => ['nullable', 'boolean'],
        ]);

        $type = $forcedType ?? (isset($data['type']) ? trim((string) $data['type']) : '');
        if ($type === '') {
            throw ValidationException::withMessages([
                'type' => ['Le type de moyen de paiement est requis.'],
            ]);
        }

        $this->assertTypeAllowedForUser($user, $type);

        $provider = strtolower(trim((string) $data['provider']));
        if ($provider === '') {
            throw ValidationException::withMessages([
                'provider' => ['Le fournisseur est requis.'],
            ]);
        }

        $details = $this->normalizeDetails($data['details'] ?? []);

        $this->assertProviderAllowedForType($provider, $type);
        $this->assertProviderDetailsValid($provider, $type, $details, $request);

        $method = DB::transaction(function () use ($data, $details, $provider, $type, $user): UserPaymentMethod {
            $isDefaultRequested = (bool) ($data['is_default'] ?? false);
            $isActive = array_key_exists('is_active', $data) ? (bool) $data['is_active'] : true;

            $hasDefault = UserPaymentMethod::query()
                ->where('user_id', $user->id)
                ->where('type', $type)
                ->where('is_active', true)
                ->where('is_default', true)
                ->exists();

            $shouldBeDefault = $isActive && ($isDefaultRequested || !$hasDefault);

            if ($shouldBeDefault) {
                UserPaymentMethod::query()
                    ->where('user_id', $user->id)
                    ->where('type', $type)
                    ->update(['is_default' => false]);
            }

            return UserPaymentMethod::create([
                'user_id' => $user->id,
                'type' => $type,
                'provider' => $provider,
                'label' => $data['label'] ?? $this->defaultLabelForProvider($provider, $type),
                'account_holder' => $data['account_holder'] ?? $user->name,
                'details' => $details,
                'is_default' => $shouldBeDefault,
                'is_active' => $isActive,
                'verified_at' => $this->shouldMarkAsVerified($provider, $details, $isActive) ? now() : null,
            ]);
        });

        return response()->json(['data' => $method], 201);
    }

    public function update(Request $request, UserPaymentMethod $paymentMethod): JsonResponse
    {
        $user = $request->user();
        abort_unless($paymentMethod->user_id === $user->id, 403);

        $data = $request->validate([
            'label' => ['sometimes', 'nullable', 'string', 'max:150'],
            'account_holder' => ['sometimes', 'nullable', 'string', 'max:255'],
            'details' => ['sometimes', 'nullable', 'array'],
            'is_default' => ['sometimes', 'boolean'],
            'is_active' => ['sometimes', 'boolean'],
        ]);

        $this->assertTypeAllowedForUser($user, $paymentMethod->type);

        if (array_key_exists('details', $data)) {
            $data['details'] = $this->normalizeDetails($data['details'] ?? []);
            $this->assertProviderDetailsValid(
                strtolower((string) $paymentMethod->provider),
                $paymentMethod->type,
                $data['details'] ?? [],
                $request,
            );
        }

        DB::transaction(function () use ($data, $paymentMethod): void {
            $requestedDefault = array_key_exists('is_default', $data)
                ? (bool) $data['is_default']
                : null;

            $requestedActive = array_key_exists('is_active', $data)
                ? (bool) $data['is_active']
                : (bool) $paymentMethod->is_active;

            if ($requestedDefault === true && $requestedActive === false) {
                throw ValidationException::withMessages([
                    'is_default' => ['Un moyen de paiement inactif ne peut pas etre defini par defaut.'],
                ]);
            }

            if ($requestedDefault === true) {
                UserPaymentMethod::query()
                    ->where('user_id', $paymentMethod->user_id)
                    ->where('type', $paymentMethod->type)
                    ->update(['is_default' => false]);
            }

            if (array_key_exists('details', $data)) {
                $provider = strtolower((string) $paymentMethod->provider);
                $data['verified_at'] = $this->shouldMarkAsVerified($provider, $data['details'] ?? [], $requestedActive)
                    ? now()
                    : null;
            }

            $paymentMethod->update($data);

            if ($requestedDefault === true) {
                UserPaymentMethod::query()
                    ->whereKey($paymentMethod->id)
                    ->update(['is_default' => true]);
                $paymentMethod->refresh();
            }

            if ($paymentMethod->is_default && $paymentMethod->is_active) {
                return;
            }

            $hasDefault = UserPaymentMethod::query()
                ->where('user_id', $paymentMethod->user_id)
                ->where('type', $paymentMethod->type)
                ->where('is_active', true)
                ->where('is_default', true)
                ->exists();

            if (!$hasDefault) {
                $fallback = UserPaymentMethod::query()
                    ->where('user_id', $paymentMethod->user_id)
                    ->where('type', $paymentMethod->type)
                    ->where('is_active', true)
                    ->latest()
                    ->first();

                if ($fallback) {
                    $fallback->update(['is_default' => true]);
                }
            }
        });

        return response()->json(['data' => $paymentMethod->fresh()]);
    }

    public function destroy(Request $request, UserPaymentMethod $paymentMethod): JsonResponse
    {
        $user = $request->user();
        abort_unless($paymentMethod->user_id === $user->id, 403);
        $this->assertTypeAllowedForUser($user, $paymentMethod->type);

        DB::transaction(function () use ($paymentMethod): void {
            $wasDefault = $paymentMethod->is_default;
            $type = $paymentMethod->type;
            $userId = $paymentMethod->user_id;

            $paymentMethod->delete();

            if (!$wasDefault) {
                return;
            }

            $fallback = UserPaymentMethod::query()
                ->where('user_id', $userId)
                ->where('type', $type)
                ->where('is_active', true)
                ->latest()
                ->first();

            if ($fallback) {
                $fallback->update(['is_default' => true]);
            }
        });

        return response()->json([], 204);
    }

    private function forcedTypeForRole(User $user): ?string
    {
        return match ($user->role) {
            'client' => UserPaymentMethod::TYPE_PAY_IN,
            'liner' => UserPaymentMethod::TYPE_PAY_OUT,
            default => null,
        };
    }

    private function assertTypeAllowedForUser(User $user, string $type): void
    {
        $forcedType = $this->forcedTypeForRole($user);
        if ($forcedType === null) {
            return;
        }

        abort_unless($type === $forcedType, 403);
    }

    private function assertProviderAllowedForType(string $provider, string $type): void
    {
        $allowed = $type === UserPaymentMethod::TYPE_PAY_IN
            ? self::PAY_IN_PROVIDERS
            : self::PAY_OUT_PROVIDERS;

        if (!in_array($provider, $allowed, true)) {
            throw ValidationException::withMessages([
                'provider' => ['Fournisseur non supporte pour ce type de moyen de paiement.'],
            ]);
        }
    }

    private function assertProviderDetailsValid(string $provider, string $type, mixed $details, ?Request $request = null): void
    {
        $detailsArray = is_array($details) ? $details : [];
        $isBackofficeRequest = $request?->user()?->role === 'admin' || $request?->boolean('allow_unverified', false) === true;

        if ($provider === 'paypal') {
            $hasEmail = isset($detailsArray['email']) && is_string($detailsArray['email']) && trim($detailsArray['email']) !== '';
            if (!$hasEmail) {
                throw ValidationException::withMessages([
                    'details.email' => ['Un email PayPal est requis.'],
                ]);
            }

            if ($type === UserPaymentMethod::TYPE_PAY_IN && !$isBackofficeRequest) {
                $hasVaultToken = isset($detailsArray['vault_token']) && is_string($detailsArray['vault_token']) && trim($detailsArray['vault_token']) !== '';
                $hasVaultId = isset($detailsArray['vault_id']) && is_string($detailsArray['vault_id']) && trim($detailsArray['vault_id']) !== '';
                $hasLegacyReference = isset($detailsArray['reference']) && is_string($detailsArray['reference']) && trim($detailsArray['reference']) !== '';

                if (!$hasVaultToken && !$hasVaultId && !$hasLegacyReference) {
                    throw ValidationException::withMessages([
                        'details.vault_token' => ['Un token PayPal Vault est requis pour le debit automatique.'],
                    ]);
                }
            }
        }

        if ($provider === 'rib') {
            if (!isset($detailsArray['iban']) || !is_string($detailsArray['iban']) || trim($detailsArray['iban']) === '') {
                throw ValidationException::withMessages([
                    'details.iban' => ['Un IBAN est requis pour un RIB.'],
                ]);
            }
        }

        if (in_array($provider, ['stripe', 'apple_pay', 'google_pay'], true)) {
            $isPayIn = $type === UserPaymentMethod::TYPE_PAY_IN;
            $strictStripeIdentifiers = !config('lineup.payments.simulate', true) && !$isBackofficeRequest;

            if ($isPayIn) {
                $hasReusableMethod = isset($detailsArray['payment_method_id']) && is_string($detailsArray['payment_method_id']) && trim($detailsArray['payment_method_id']) !== '';
                $hasLegacyReference = isset($detailsArray['reference']) && is_string($detailsArray['reference']) && trim($detailsArray['reference']) !== '';
                $hasWalletToken = isset($detailsArray['device_account']) && is_string($detailsArray['device_account']) && trim($detailsArray['device_account']) !== '';

                if (!$hasReusableMethod && !$hasLegacyReference && !$hasWalletToken && !$isBackofficeRequest) {
                    throw ValidationException::withMessages([
                        'details.payment_method_id' => ['Un identifiant Stripe PaymentMethod est requis pour ce provider.'],
                    ]);
                }

                $candidatePaymentMethod = (string) (
                    $detailsArray['payment_method_id']
                    ?? $detailsArray['reference']
                    ?? $detailsArray['device_account']
                    ?? ''
                );
                $candidatePaymentMethod = trim($candidatePaymentMethod);
                $normalizedPaymentMethod = strtolower($candidatePaymentMethod);
                $isPlaceholder = $normalizedPaymentMethod === 'pm_xxx'
                    || str_starts_with($normalizedPaymentMethod, 'pm_demo_')
                    || str_starts_with($normalizedPaymentMethod, 'pm_test_');

                if (
                    $strictStripeIdentifiers
                    && $candidatePaymentMethod !== ''
                    && ($isPlaceholder || !str_starts_with($candidatePaymentMethod, 'pm_'))
                ) {
                    throw ValidationException::withMessages([
                        'details.payment_method_id' => ['PaymentMethod Stripe invalide. Utilisez un identifiant reel au format pm_...'],
                    ]);
                }
            } else {
                $hasDestination = isset($detailsArray['destination_account']) && is_string($detailsArray['destination_account']) && trim($detailsArray['destination_account']) !== '';
                $hasAccountId = isset($detailsArray['account_id']) && is_string($detailsArray['account_id']) && trim($detailsArray['account_id']) !== '';
                $hasLegacyReference = isset($detailsArray['reference']) && is_string($detailsArray['reference']) && trim($detailsArray['reference']) !== '';
                $hasWalletToken = isset($detailsArray['device_account']) && is_string($detailsArray['device_account']) && trim($detailsArray['device_account']) !== '';

                if (!$hasDestination && !$hasAccountId && !$hasLegacyReference && !$hasWalletToken && !$isBackofficeRequest) {
                    throw ValidationException::withMessages([
                        'details.destination_account' => ['Un compte destination Stripe Connect est requis pour les retraits.'],
                    ]);
                }

                $destinationCandidate = (string) (
                    $detailsArray['destination_account']
                    ?? $detailsArray['account_id']
                    ?? $detailsArray['reference']
                    ?? $detailsArray['device_account']
                    ?? ''
                );
                $destinationCandidate = trim($destinationCandidate);
                $normalizedDestination = strtolower($destinationCandidate);
                $isDestinationPlaceholder = $normalizedDestination === 'acct_xxx'
                    || str_starts_with($normalizedDestination, 'acct_demo_')
                    || str_starts_with($normalizedDestination, 'acct_test_');

                if (
                    $strictStripeIdentifiers
                    && $destinationCandidate !== ''
                    && ($isDestinationPlaceholder || !str_starts_with($destinationCandidate, 'acct_'))
                ) {
                    throw ValidationException::withMessages([
                        'details.destination_account' => ['Compte destination Stripe invalide. Utilisez un identifiant reel au format acct_...'],
                    ]);
                }
            }
        }
    }

    private function normalizeDetails(mixed $details): array
    {
        if (!is_array($details)) {
            return [];
        }

        $normalized = [];
        foreach ($details as $key => $value) {
            if (is_string($value)) {
                $normalized[$key] = trim($value);
                continue;
            }

            $normalized[$key] = $value;
        }

        return $normalized;
    }

    private function defaultLabelForProvider(string $provider, string $type): string
    {
        return match ($provider) {
            'rib' => 'RIB',
            'paypal' => $type === UserPaymentMethod::TYPE_PAY_IN ? 'PayPal' : 'Retrait PayPal',
            'stripe' => $type === UserPaymentMethod::TYPE_PAY_IN ? 'Carte Stripe' : 'Retrait Stripe',
            'apple_pay' => 'Apple Pay',
            'google_pay' => 'Google Pay',
            default => ucfirst(str_replace('_', ' ', $provider)),
        };
    }

    private function shouldMarkAsVerified(string $provider, array $details, bool $isActive): bool
    {
        if (!$isActive) {
            return false;
        }

        return match ($provider) {
            'rib' => !empty($details['iban']),
            'paypal' => !empty($details['vault_token']) || !empty($details['vault_id']) || !empty($details['email']),
            'stripe', 'apple_pay', 'google_pay' => !empty($details['payment_method_id'])
                || !empty($details['destination_account'])
                || !empty($details['account_id'])
                || !empty($details['reference'])
                || !empty($details['device_account']),
            default => false,
        };
    }
}
