<?php

use App\Models\User;
use Modules\Booking\Enums\BookingStatus;
use Modules\Booking\Models\Booking;
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\RefundStatus;
use Modules\Payment\Factories\PaymentGatewayFactory;
use Modules\Payment\Models\Payment;
use Spatie\Permission\Models\Permission;

/**
 * Never calls the real KBZ refund API in tests.
 */
class FakeCancelApiRefundGateway implements PaymentGatewayInterface
{
    public function initiate(PaymentRequestData $data): PaymentResultData
    {
        throw new RuntimeException('not needed for this test');
    }

    public function verify(string $gatewayTransactionId): PaymentResultData
    {
        throw new RuntimeException('not needed for this test');
    }

    public function refund(string $gatewayTransactionId, string $amount, string $reason): RefundResultData
    {
        return new RefundResultData(status: RefundStatus::Completed, gatewayRefundId: 'REFUND123', gatewayPayload: []);
    }

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

beforeEach(function () {
    foreach (['manage_bookings', 'process_refunds'] as $permission) {
        Permission::findOrCreate($permission, 'web');
    }

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

    $this->owner = User::factory()->create();
    $this->token = $this->owner->createToken('test-token')->plainTextToken;
});

test('the owner can cancel their own pending_payment booking', function () {
    $booking = Booking::factory()->create(['user_id' => $this->owner->id, 'status' => BookingStatus::PendingPayment]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson("/api/v1/bookings/{$booking->booking_ref}/cancel")
        ->assertSuccessful()
        ->assertJsonPath('data.status', BookingStatus::Cancelled->value);

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

test('the owner cannot cancel their own confirmed booking without process_refunds', function () {
    $booking = Booking::factory()->create(['user_id' => $this->owner->id, 'status' => BookingStatus::Confirmed]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson("/api/v1/bookings/{$booking->booking_ref}/cancel")
        ->assertForbidden();

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

test('staff with process_refunds can cancel a confirmed booking, which refunds it in full', function () {
    $staff = User::factory()->create()->givePermissionTo('process_refunds');
    $staffToken = $staff->createToken('staff-token')->plainTextToken;

    $booking = Booking::factory()->create(['user_id' => $this->owner->id, 'status' => BookingStatus::Confirmed, 'price' => 15000]);
    Payment::factory()->completed()->create([
        'booking_id' => $booking->id,
        'gateway' => PaymentMethod::KbzMiniApp,
        'amount' => 15000,
        'gateway_transaction_id' => 'EVB-CANCEL-API-1',
    ]);

    $this->withHeader('Authorization', "Bearer {$staffToken}")
        ->postJson("/api/v1/bookings/{$booking->booking_ref}/cancel")
        ->assertSuccessful()
        ->assertJsonPath('data.status', BookingStatus::Cancelled->value);

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

test('cancelling a confirmed booking with no completed payment surfaces as 422 and leaves it untouched', function () {
    $staff = User::factory()->create()->givePermissionTo('process_refunds');
    $staffToken = $staff->createToken('staff-token')->plainTextToken;

    $booking = Booking::factory()->create(['user_id' => $this->owner->id, 'status' => BookingStatus::Confirmed]);

    $this->withHeader('Authorization', "Bearer {$staffToken}")
        ->postJson("/api/v1/bookings/{$booking->booking_ref}/cancel")
        ->assertStatus(422);

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

test('a non-owner without manage_bookings cannot cancel someone else\'s booking', function () {
    $booking = Booking::factory()->create([
        'user_id' => User::factory()->create()->id,
        'status' => BookingStatus::PendingPayment,
    ]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson("/api/v1/bookings/{$booking->booking_ref}/cancel")
        ->assertForbidden();

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

test('staff with manage_bookings can cancel someone else\'s pending_payment booking', function () {
    $staff = User::factory()->create()->givePermissionTo('manage_bookings');
    $staffToken = $staff->createToken('staff-token')->plainTextToken;

    $booking = Booking::factory()->create(['user_id' => $this->owner->id, 'status' => BookingStatus::PendingPayment]);

    $this->withHeader('Authorization', "Bearer {$staffToken}")
        ->postJson("/api/v1/bookings/{$booking->booking_ref}/cancel")
        ->assertSuccessful();

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

test('unauthenticated requests are rejected', function () {
    $booking = Booking::factory()->create(['status' => BookingStatus::PendingPayment]);

    $this->postJson("/api/v1/bookings/{$booking->booking_ref}/cancel")->assertUnauthorized();
});

test('404s for a booking that does not exist', function () {
    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings/EVB-DOES-NOT-EXIST/cancel')
        ->assertNotFound();
});
