<?php

namespace Modules\Payment\Filament\Resources\Refunds\Actions;

use Filament\Actions\Action;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Support\Icons\Heroicon;
use Modules\Payment\Actions\RefundBookingAction;
use Modules\Payment\Enums\PaymentStatus;
use Modules\Payment\Exceptions\RefundFailedException;
use Modules\Payment\Exceptions\RefundNotAllowedException;
use Modules\Payment\Models\Payment;

/**
 * Header action on ListRefunds — staff pick a successful Payment (only
 * Completed ones are offered, domain.md §6) and an amount/reason, which
 * calls RefundBookingAction the same way the API endpoint does (T5.11).
 */
class ProcessRefundAction
{
    public static function make(): Action
    {
        return Action::make('process')
            ->label('Process Refund')
            ->icon(Heroicon::OutlinedReceiptRefund)
            ->color('danger')
            ->visible(fn (): bool => auth()->user()?->can('process_refunds') ?? false)
            ->schema([
                Select::make('payment_id')
                    ->label('Payment')
                    ->options(fn () => Payment::query()
                        ->where('status', PaymentStatus::Completed->value)
                        // A soft-deleted booking excludes itself from this
                        // belongsTo by default — never offer a payment whose
                        // booking is gone (Booking now uses SoftDeletes).
                        ->whereHas('booking')
                        ->with('booking')
                        ->get()
                        ->mapWithKeys(fn (Payment $payment) => [
                            $payment->id => "{$payment->booking?->booking_ref} — {$payment->amount} {$payment->currency} (#{$payment->id})",
                        ]))
                    ->searchable()
                    ->live()
                    ->required(),
                Toggle::make('full_refund')
                    ->label('Full refund')
                    ->live()
                    ->default(true)
                    ->helperText(fn (Get $get): string => $get('payment_id')
                        ? 'Refundable balance: '.(Payment::find($get('payment_id'))?->refundableBalance() ?? '0.00')
                        : 'Select a payment to see its refundable balance.'),
                TextInput::make('amount')
                    ->numeric()
                    ->minValue(0.01)
                    ->visible(fn (Get $get): bool => ! $get('full_refund'))
                    ->required(fn (Get $get): bool => ! $get('full_refund'))
                    ->maxValue(fn (Get $get): ?string => Payment::find($get('payment_id'))?->refundableBalance()),
                Textarea::make('reason')
                    ->required(),
            ])
            ->action(function (array $data): void {
                $payment = Payment::with('booking')->findOrFail($data['payment_id']);

                // Defense in depth against the options list going stale
                // between render and submit (e.g. the booking gets deleted
                // mid-form) — $payment->booking is nullable, but
                // RefundBookingAction requires a real Booking.
                if ($payment->booking === null) {
                    Notification::make()
                        ->title('Refund failed')
                        ->body('This payment\'s booking no longer exists.')
                        ->danger()
                        ->send();

                    return;
                }

                $amount = $data['full_refund'] ? $payment->refundableBalance() : (string) $data['amount'];

                try {
                    app(RefundBookingAction::class)->handle(
                        $payment->booking,
                        $amount,
                        $data['reason'],
                        auth()->id(),
                    );

                    Notification::make()
                        ->title('Refund processed')
                        ->success()
                        ->send();
                } catch (RefundNotAllowedException|RefundFailedException $exception) {
                    Notification::make()
                        ->title('Refund failed')
                        ->body($exception->getMessage())
                        ->danger()
                        ->send();
                }
            });
    }
}
