<?php

use Illuminate\Support\Facades\Event;
use Modules\Booking\Enums\BookingStatus;
use Modules\Booking\Models\Booking;
use Modules\Payment\Actions\ConfirmPaymentAction;
use Modules\Payment\Contracts\PaymentGatewayInterface;
use Modules\Payment\Data\PaymentRequestData;
use Modules\Payment\Data\PaymentResultData;
use Modules\Payment\Data\RefundResultData;
use Modules\Payment\Enums\PaymentMethod;
use Modules\Payment\Enums\PaymentStatus;
use Modules\Payment\Events\PaymentCompleted;
use Modules\Payment\Events\PaymentFailed;
use Modules\Payment\Factories\PaymentGatewayFactory;
use Modules\Payment\Models\Payment;

/**
 * Fake gateway standing in for KbzMiniAppGateway::verify() — never hits the
 * real KBZ API in tests, and counts calls so idempotency (only one real
 * re-verification per Payment) is provable.
 */
class FakeConfirmGateway implements PaymentGatewayInterface
{
    public static int $verifyCallCount = 0;

    public static PaymentStatus $verifyStatus = PaymentStatus::Completed;

    public function initiate(PaymentRequestData $data): PaymentResultData
    {
        throw new RuntimeException('not needed for this test');
    }

    public function verify(string $gatewayTransactionId): PaymentResultData
    {
        self::$verifyCallCount++;

        return new PaymentResultData(
            status: self::$verifyStatus,
            gatewayTransactionId: $gatewayTransactionId,
            gatewayPayload: ['trade_status' => self::$verifyStatus->value],
        );
    }

    public function refund(string $gatewayTransactionId, string $amount, string $reason): RefundResultData
    {
        throw new RuntimeException('not needed for this test');
    }

    public function handleWebhook(array $payload): PaymentResultData
    {
        throw new RuntimeException('not needed for this test');
    }
}

beforeEach(function () {
    FakeConfirmGateway::$verifyCallCount = 0;
    FakeConfirmGateway::$verifyStatus = PaymentStatus::Completed;

    app(PaymentGatewayFactory::class)->register(PaymentMethod::KbzMiniApp, FakeConfirmGateway::class);
});

test('confirming a pending payment as completed dispatches PaymentCompleted once', function () {
    Event::fake([PaymentCompleted::class, PaymentFailed::class]);

    $payment = Payment::factory()->create([
        'status' => PaymentStatus::Pending,
        'gateway' => PaymentMethod::KbzMiniApp,
        'gateway_transaction_id' => 'EVB-TXN-001',
    ]);

    $result = app(ConfirmPaymentAction::class)->handle(PaymentMethod::KbzMiniApp, 'EVB-TXN-001');

    expect($result->id)->toBe($payment->id)
        ->and($result->status)->toBe(PaymentStatus::Completed)
        ->and(FakeConfirmGateway::$verifyCallCount)->toBe(1);

    Event::assertDispatchedTimes(PaymentCompleted::class, 1);
    Event::assertNotDispatched(PaymentFailed::class);
});

test('a redelivered confirmation for an already-completed payment is a no-op', function () {
    Event::fake([PaymentCompleted::class, PaymentFailed::class]);

    Payment::factory()->create([
        'status' => PaymentStatus::Pending,
        'gateway' => PaymentMethod::KbzMiniApp,
        'gateway_transaction_id' => 'EVB-TXN-002',
    ]);

    $action = app(ConfirmPaymentAction::class);
    $action->handle(PaymentMethod::KbzMiniApp, 'EVB-TXN-002');
    $second = $action->handle(PaymentMethod::KbzMiniApp, 'EVB-TXN-002');

    expect($second->status)->toBe(PaymentStatus::Completed)
        ->and(FakeConfirmGateway::$verifyCallCount)->toBe(1);

    Event::assertDispatchedTimes(PaymentCompleted::class, 1);
});

test('confirming a payment the gateway reports as failed dispatches PaymentFailed and leaves the booking untouched', function () {
    Event::fake([PaymentCompleted::class, PaymentFailed::class]);
    FakeConfirmGateway::$verifyStatus = PaymentStatus::Failed;

    $payment = Payment::factory()->create([
        'status' => PaymentStatus::Pending,
        'gateway' => PaymentMethod::KbzMiniApp,
        'gateway_transaction_id' => 'EVB-TXN-003',
    ]);

    $result = app(ConfirmPaymentAction::class)->handle(PaymentMethod::KbzMiniApp, 'EVB-TXN-003');

    expect($result->status)->toBe(PaymentStatus::Failed);
    Event::assertDispatchedTimes(PaymentFailed::class, 1);
    Event::assertNotDispatched(PaymentCompleted::class);
});

test('a gateway verify still reporting pending leaves the payment pending and dispatches nothing', function () {
    Event::fake([PaymentCompleted::class, PaymentFailed::class]);
    FakeConfirmGateway::$verifyStatus = PaymentStatus::Pending;

    Payment::factory()->create([
        'status' => PaymentStatus::Pending,
        'gateway' => PaymentMethod::KbzMiniApp,
        'gateway_transaction_id' => 'EVB-TXN-004',
    ]);

    $result = app(ConfirmPaymentAction::class)->handle(PaymentMethod::KbzMiniApp, 'EVB-TXN-004');

    expect($result->status)->toBe(PaymentStatus::Pending);
    Event::assertNothingDispatched();
});

test('returns null when no payment matches the gateway transaction id', function () {
    $result = app(ConfirmPaymentAction::class)->handle(PaymentMethod::KbzMiniApp, 'does-not-exist');

    expect($result)->toBeNull();
});

test('confirming a completed payment flips its booking to confirmed via MarkBookingPaid', function () {
    $booking = Booking::factory()->create(['status' => BookingStatus::PendingPayment]);

    Payment::factory()->create([
        'booking_id' => $booking->id,
        'status' => PaymentStatus::Pending,
        'gateway' => PaymentMethod::KbzMiniApp,
        'gateway_transaction_id' => 'EVB-TXN-005',
    ]);

    app(ConfirmPaymentAction::class)->handle(PaymentMethod::KbzMiniApp, 'EVB-TXN-005');

    expect($booking->refresh()->status)->toBe(BookingStatus::Confirmed);
});
