<?php

namespace App\Services\Payments;

use App\Models\Payout;
use App\Models\UserPaymentMethod;
use App\Models\Wallet;
use App\Models\WalletTransaction;
use App\Services\InAppNotificationService;
use Illuminate\Support\Facades\DB;

class PayoutProcessor
{
    public function __construct(
        private readonly PaymentGatewayManager $gatewayManager,
        private readonly InAppNotificationService $notificationService,
    ) {}

    public function processPayout(int $payoutId): void
    {
        $payout = Payout::query()
            ->with(['paymentMethod', 'liner:id,name'])
            ->find($payoutId);

        if (!$payout) {
            return;
        }

        if ($payout->status === 'paid') {
            return;
        }

        $paymentMethod = $payout->paymentMethod;
        if (!$paymentMethod) {
            if ($payout->provider === 'rib') {
                $this->markPayoutPending($payout, 'Traitement RIB manuel en attente.');
                return;
            }

            $this->markPayoutFailed($payout, 'Moyen de paiement de retrait introuvable.');
            return;
        }

        $result = $this->gatewayManager->payout(
            method: $paymentMethod,
            amount: (float) $payout->amount,
            currency: $payout->currency,
            metadata: [
                'payout_id' => $payout->id,
                'liner_id' => $payout->liner_id,
            ],
        );

        if ($result->status === GatewayResult::STATUS_PAID) {
            $this->markPayoutPaid($payout, $result->providerReference, $result->payload);
            return;
        }

        if ($result->status === GatewayResult::STATUS_PENDING) {
            $this->markPayoutPending(
                $payout,
                $result->message,
                $result->providerReference,
                $result->payload,
            );
            return;
        }

        $this->markPayoutFailed(
            $payout,
            $result->message ?: 'Retrait refuse par le fournisseur.',
            $result->providerReference,
            $result->payload,
        );
    }

    public function markPayoutPaid(
        Payout $payout,
        ?string $providerReference = null,
        array $metadata = [],
    ): void {
        $currentMetadata = is_array($payout->metadata) ? $payout->metadata : [];
        $payout->update([
            'status' => 'paid',
            'paid_at' => now(),
            'failure_reason' => null,
            'provider_reference' => $providerReference ?? $payout->provider_reference,
            'metadata' => array_merge($currentMetadata, $metadata),
        ]);

        $this->notificationService->notifyUser(
            $payout->liner_id,
            'payout.paid',
            'Retrait valide',
            sprintf('Votre retrait de %.2f %s a ete valide.', (float) $payout->amount, $payout->currency),
            [
                'payout_id' => $payout->id,
                'amount' => (float) $payout->amount,
                'currency' => $payout->currency,
            ],
        );
    }

    public function markPayoutPending(
        Payout $payout,
        ?string $message = null,
        ?string $providerReference = null,
        array $metadata = [],
    ): void {
        $currentMetadata = is_array($payout->metadata) ? $payout->metadata : [];
        $payout->update([
            'status' => 'pending',
            'failure_reason' => $message,
            'provider_reference' => $providerReference ?? $payout->provider_reference,
            'metadata' => array_merge($currentMetadata, $metadata),
        ]);

        $this->notificationService->notifyUser(
            $payout->liner_id,
            'payout.pending',
            'Retrait en cours',
            sprintf('Votre retrait de %.2f %s est en cours de traitement.', (float) $payout->amount, $payout->currency),
            [
                'payout_id' => $payout->id,
                'amount' => (float) $payout->amount,
                'currency' => $payout->currency,
            ],
        );
    }

    public function markPayoutFailed(
        Payout $payout,
        string $reason,
        ?string $providerReference = null,
        array $metadata = [],
    ): void {
        DB::transaction(function () use ($payout, $reason, $providerReference, $metadata): void {
            $currentMetadata = is_array($payout->metadata) ? $payout->metadata : [];
            $payout->update([
                'status' => 'failed',
                'failure_reason' => $reason,
                'provider_reference' => $providerReference ?? $payout->provider_reference,
                'metadata' => array_merge($currentMetadata, $metadata),
            ]);

            $this->refundWalletIfNeeded($payout, $reason);
        });

        $this->notificationService->notifyUser(
            $payout->liner_id,
            'payout.failed',
            'Retrait echoue',
            sprintf('Votre retrait de %.2f %s a echoue (%s).', (float) $payout->amount, $payout->currency, $reason),
            [
                'payout_id' => $payout->id,
                'amount' => (float) $payout->amount,
                'currency' => $payout->currency,
                'reason' => $reason,
            ],
        );
    }

    public function markPayoutPaidByReference(string $providerReference, array $metadata = []): bool
    {
        $payout = Payout::query()
            ->where('provider_reference', $providerReference)
            ->first();

        if (!$payout) {
            return false;
        }

        $this->markPayoutPaid($payout, providerReference: $providerReference, metadata: $metadata);

        return true;
    }

    public function markPayoutFailedByReference(string $providerReference, string $reason, array $metadata = []): bool
    {
        $payout = Payout::query()
            ->where('provider_reference', $providerReference)
            ->first();

        if (!$payout) {
            return false;
        }

        $this->markPayoutFailed($payout, reason: $reason, providerReference: $providerReference, metadata: $metadata);

        return true;
    }

    private function refundWalletIfNeeded(Payout $payout, string $reason): void
    {
        $alreadyRefunded = WalletTransaction::query()
            ->where('reference_type', Payout::class)
            ->where('reference_id', $payout->id)
            ->where('type', 'credit')
            ->exists();

        if ($alreadyRefunded) {
            return;
        }

        $wallet = Wallet::firstOrCreate(
            ['user_id' => $payout->liner_id],
            ['balance' => 0, 'currency' => $payout->currency]
        );

        $wallet->increment('balance', (float) $payout->amount);

        WalletTransaction::create([
            'wallet_id' => $wallet->id,
            'user_id' => $payout->liner_id,
            'type' => 'credit',
            'amount' => $payout->amount,
            'description' => 'Remboursement retrait',
            'reference_type' => Payout::class,
            'reference_id' => $payout->id,
            'metadata' => [
                'reason' => $reason,
            ],
        ]);
    }
}
