<?php

namespace App\Services;

use App\Models\PayoutTransaction;
use App\Models\Withdrawal;
use Illuminate\Support\Facades\DB;

class PayoutService
{
    public function __construct(private readonly WalletService $walletService)
    {
    }

    public function initiate(Withdrawal $withdrawal, ?int $processedBy): PayoutTransaction
    {
        return DB::transaction(function () use ($withdrawal, $processedBy): PayoutTransaction {
            $existing = PayoutTransaction::query()
                ->where('withdrawal_id', $withdrawal->id)
                ->whereIn('status', ['initiated', 'processing', 'success'])
                ->latest('id')
                ->first();

            if ($existing) {
                return $existing;
            }

            $gateway = (string) setting('payout_gateway', 'manual');

            $payout = PayoutTransaction::query()->create([
                'withdrawal_id' => $withdrawal->id,
                'user_id' => $withdrawal->user_id,
                'gateway' => $gateway,
                'amount' => (float) $withdrawal->net_amount,
                'status' => 'initiated',
                'attempts' => 0,
            ]);

            $withdrawal->status = 'processing';
            $withdrawal->processed_by = $processedBy;
            $withdrawal->processed_at = now();
            $withdrawal->save();

            return $payout;
        });
    }

    public function attempt(PayoutTransaction $payout): PayoutTransaction
    {
        if ($payout->status === 'success') {
            return $payout;
        }

        $gateway = $payout->gateway ?: (string) setting('payout_gateway', 'manual');

        try {
            if ($gateway === 'manual') {
                return $this->markSuccess($payout, ['note' => 'manual payout']);
            }

            return $this->markRetry($payout, "Gateway {$gateway} not configured.");
        } catch (\Throwable $exception) {
            return $this->markFailed($payout, $exception->getMessage());
        }
    }

    private function markSuccess(PayoutTransaction $payout, array $payload = []): PayoutTransaction
    {
        return DB::transaction(function () use ($payout, $payload): PayoutTransaction {
            $withdrawal = $payout->withdrawal;

            $payout->status = 'success';
            $payout->attempts = min(255, $payout->attempts + 1);
            $payout->last_attempt_at = now();
            $payout->processed_at = now();
            $payout->response_payload = array_merge($payout->response_payload ?? [], $payload);
            $payout->failure_reason = null;
            $payout->save();

            if ($withdrawal) {
                $withdrawal->status = 'paid';
                $withdrawal->processed_at = now();
                $withdrawal->locked_amount = 0;
                $withdrawal->save();

                $this->walletService->debit(
                    $withdrawal->user_id,
                    'locked',
                    (float) $withdrawal->amount,
                    'payout',
                    $payout->id,
                    [
                        'reference' => 'PAYOUT-'.$payout->id,
                        'display_reference' => $withdrawal->request_number,
                        'narration' => 'Withdrawal payout completed',
                    ]
                );
            }

            return $payout;
        });
    }

    private function markRetry(PayoutTransaction $payout, string $reason): PayoutTransaction
    {
        $payout->status = 'retry';
        $payout->attempts = min(255, $payout->attempts + 1);
        $payout->last_attempt_at = now();
        $payout->failure_reason = $reason;
        $payout->save();

        if ($payout->withdrawal && $payout->withdrawal->status === 'approved') {
            $payout->withdrawal->status = 'processing';
            $payout->withdrawal->save();
        }

        return $payout;
    }

    private function markFailed(PayoutTransaction $payout, string $reason): PayoutTransaction
    {
        $payout->status = 'failed';
        $payout->attempts = min(255, $payout->attempts + 1);
        $payout->last_attempt_at = now();
        $payout->failure_reason = $reason;
        $payout->save();

        if ($payout->withdrawal && $payout->withdrawal->status !== 'paid') {
            $withdrawal = $payout->withdrawal;
            $withdrawal->status = 'failed';
            $withdrawal->save();

            if ((float) $withdrawal->locked_amount > 0) {
                $this->walletService->releaseLock(
                    $withdrawal->user_id,
                    (float) $withdrawal->locked_amount,
                    true,
                    [
                        'display_reference' => $withdrawal->request_number,
                        'reference_debit' => 'PAYOUT-'.$payout->id.'-FAIL-D',
                        'reference_credit' => 'PAYOUT-'.$payout->id.'-FAIL-C',
                        'narration' => 'Withdrawal payout failed',
                    ]
                );

                $withdrawal->locked_amount = 0;
                $withdrawal->save();
            }
        }

        return $payout;
    }
}
