<?php

use App\Models\User;
use Modules\Booking\Enums\BookingChannel;
use Modules\Booking\Enums\BookingStatus;
use Modules\Booking\Models\Booking;
use Modules\Catalog\Models\DepartureTimeSlot;
use Modules\Routing\Models\EvRoute;
use Modules\Routing\Models\RoutePricing;
use Modules\Shared\Enums\VehicleOption;

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

/**
 * @param  array<int, array{0: VehicleOption, 1: string}>  $pricedOptions
 */
function bookableRouteAndSlot(array $pricedOptions): array
{
    $route = EvRoute::factory()->create(['is_active' => true]);
    $timeSlot = DepartureTimeSlot::factory()->create();
    $route->timeSlots()->attach($timeSlot->id, ['is_active' => true]);

    foreach ($pricedOptions as [$vehicleOption, $price]) {
        RoutePricing::factory()->create([
            'ev_route_id' => $route->id,
            'vehicle_option' => $vehicleOption,
            'price' => $price,
        ]);
    }

    return [$route, $timeSlot];
}

/**
 * @param  array<int, array{vehicle_option: string, passenger_count: int}>  $selections
 */
function bookingPayload(EvRoute $route, DepartureTimeSlot $timeSlot, array $selections): array
{
    return [
        'ev_route_id' => $route->id,
        'departure_time_slot_id' => $timeSlot->id,
        'travel_date' => now()->addDay()->toDateString(),
        'selections' => $selections,
        'passenger_name' => 'Jane Doe',
        'passenger_phone' => '+959123456789',
        'pickup_address' => '123 Pickup St',
        'dropoff_address' => '456 Dropoff Ave',
    ];
}

test('happy path: it creates a pending_payment booking with a snapshotted price', function () {
    config(['booking.back_seat_enabled' => true]);

    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '15000.00']]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
        ]))
        ->assertCreated()
        ->assertJsonPath('data.status', BookingStatus::PendingPayment->value)
        ->assertJsonPath('data.price', '15000.00')
        ->assertJsonPath('data.vehicle_options.0.vehicle_option', VehicleOption::BackSeat->value)
        ->assertJsonPath('data.route.id', $route->id)
        ->assertJsonPath('data.time_slot.id', $timeSlot->id);

    expect(Booking::count())->toBe(1);
});

test('happy path: front seat and back seat can be booked together', function () {
    config(['booking.back_seat_enabled' => true]);

    [$route, $timeSlot] = bookableRouteAndSlot([
        [VehicleOption::FrontSeat, '12000.00'],
        [VehicleOption::BackSeat, '9000.00'],
    ]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'front_seat', 'passenger_count' => 1],
            ['vehicle_option' => 'back_seat', 'passenger_count' => 2],
        ]))
        ->assertCreated()
        ->assertJsonPath('data.price', '30000.00')
        ->assertJsonCount(2, 'data.vehicle_options');
});

test('front-seat-limit rejection surfaces as 422', function () {
    config(['booking.front_seat_max_per_booking' => 1]);

    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::FrontSeat, '12000.00']]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'front_seat', 'passenger_count' => 2],
        ]))
        ->assertStatus(422)
        ->assertJsonPath('message', 'Vehicle option [front_seat] passenger count [2] exceeds the max of [1] per booking.');

    expect(Booking::count())->toBe(0);
});

test('back-seat-limit rejection surfaces as 422', function () {
    config(['booking.back_seat_max_per_booking' => 1]);

    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '9000.00']]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'back_seat', 'passenger_count' => 2],
        ]))
        ->assertStatus(422)
        ->assertJsonPath('message', 'Vehicle option [back_seat] passenger count [2] exceeds the max of [1] per booking.');

    expect(Booking::count())->toBe(0);
});

test('whole-vehicle-limit rejection surfaces as 422', function () {
    config(['booking.whole_vehicle_max_per_booking' => 1]);

    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::WholeVehicle, '30000.00']]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'whole_vehicle', 'passenger_count' => 2],
        ]))
        ->assertStatus(422)
        ->assertJsonPath('message', 'Vehicle option [whole_vehicle] passenger count [2] exceeds the max of [1] per booking.');

    expect(Booking::count())->toBe(0);
});

test('disabled-vehicle-option rejection surfaces as 422', function () {
    config(['booking.whole_vehicle_enabled' => false]);

    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::WholeVehicle, '30000.00']]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'whole_vehicle', 'passenger_count' => 1],
        ]))
        ->assertStatus(422)
        ->assertJsonPath('message', 'Vehicle option [whole_vehicle] is not currently available for booking.');

    expect(Booking::count())->toBe(0);
});

test('mixing whole vehicle with another option surfaces as 422', function () {
    config([
        'booking.back_seat_enabled' => true,
        'booking.whole_vehicle_enabled' => true,
    ]);

    [$route, $timeSlot] = bookableRouteAndSlot([
        [VehicleOption::WholeVehicle, '30000.00'],
        [VehicleOption::BackSeat, '9000.00'],
    ]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'whole_vehicle', 'passenger_count' => 1],
            ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
        ]))
        ->assertStatus(422);

    expect(Booking::count())->toBe(0);
});

test('unauthenticated requests are rejected', function () {
    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '15000.00']]);

    $this->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
        ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
    ]))->assertUnauthorized();
});

test('shape validation rejects a missing required field', function () {
    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', [])
        ->assertStatus(422)
        ->assertJsonValidationErrors([
            'ev_route_id', 'departure_time_slot_id', 'travel_date', 'selections',
            'passenger_name', 'passenger_phone', 'pickup_address', 'dropoff_address',
        ]);
});

test('shape validation rejects an invalid vehicle_option value', function () {
    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '15000.00']]);

    $payload = bookingPayload($route, $timeSlot, [
        ['vehicle_option' => 'business_class', 'passenger_count' => 1],
    ]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', $payload)
        ->assertStatus(422)
        ->assertJsonValidationErrors(['selections.0.vehicle_option']);
});

test('shape validation rejects an empty selections array', function () {
    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '15000.00']]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, []))
        ->assertStatus(422)
        ->assertJsonValidationErrors(['selections']);
});

test('created_by_channel defaults to kbz_miniapp when no Device-Type header is sent', function () {
    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '15000.00']]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
        ]))
        ->assertCreated()
        ->assertJsonPath('data.created_by_channel', BookingChannel::MiniApp->value);
});

test('created_by_channel is taken from the Device-Type header', function (string $deviceType, BookingChannel $expected) {
    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '15000.00']]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->withHeader('Device-Type', $deviceType)
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
        ]))
        ->assertCreated()
        ->assertJsonPath('data.created_by_channel', $expected->value);
})->with([
    'android' => ['android', BookingChannel::Android],
    'ios' => ['ios', BookingChannel::Ios],
    'web' => ['web', BookingChannel::Web],
    'kbz_miniapp' => ['kbz_miniapp', BookingChannel::MiniApp],
]);

test('customer-supplied notes are stored and returned', function () {
    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '15000.00']]);

    $payload = bookingPayload($route, $timeSlot, [
        ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
    ]);
    $payload['notes'] = 'Please call before arriving.';

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', $payload)
        ->assertCreated()
        ->assertJsonPath('data.notes', 'Please call before arriving.');

    expect(Booking::first()->notes)->toBe('Please call before arriving.');
});

test('notes is optional and defaults to null', function () {
    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '15000.00']]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
        ]))
        ->assertCreated()
        ->assertJsonPath('data.notes', null);
});

test('a Device-Type header cannot spoof the agent or admin channel', function (string $deviceType) {
    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '15000.00']]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->withHeader('Device-Type', $deviceType)
        ->postJson('/api/v1/bookings', bookingPayload($route, $timeSlot, [
            ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
        ]))
        ->assertCreated()
        ->assertJsonPath('data.created_by_channel', BookingChannel::MiniApp->value);
})->with([
    'agent' => ['agent'],
    'admin' => ['admin'],
    'unrecognized value' => ['smart-fridge'],
]);

/**
 * Same company as $outbound, from/to swapped — the true reverse route.
 *
 * @param  array<int, array{0: VehicleOption, 1: string}>  $pricedOptions
 */
function reverseRouteAndSlot(EvRoute $outbound, array $pricedOptions): array
{
    $route = EvRoute::factory()->create([
        'ev_company_id' => $outbound->ev_company_id,
        'from_destination_id' => $outbound->to_destination_id,
        'to_destination_id' => $outbound->from_destination_id,
        'is_active' => true,
    ]);
    $timeSlot = DepartureTimeSlot::factory()->create();
    $route->timeSlots()->attach($timeSlot->id, ['is_active' => true]);

    foreach ($pricedOptions as [$vehicleOption, $price]) {
        RoutePricing::factory()->create([
            'ev_route_id' => $route->id,
            'vehicle_option' => $vehicleOption,
            'price' => $price,
        ]);
    }

    return [$route, $timeSlot];
}

test('round trip: creates two linked bookings, each priced against its own route', function () {
    config(['booking.back_seat_enabled' => true]);

    [$outboundRoute, $outboundSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '9000.00']]);
    [$returnRoute, $returnSlot] = reverseRouteAndSlot($outboundRoute, [[VehicleOption::BackSeat, '11000.00']]);

    $payload = bookingPayload($outboundRoute, $outboundSlot, [
        ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
    ]);
    $payload['is_round_trip'] = true;
    $payload['return_ev_route_id'] = $returnRoute->id;
    $payload['return_departure_time_slot_id'] = $returnSlot->id;
    $payload['return_travel_date'] = now()->addDays(3)->toDateString();
    $payload['return_selections'] = [['vehicle_option' => 'back_seat', 'passenger_count' => 1]];

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', $payload)
        ->assertCreated()
        ->assertJsonPath('data.is_round_trip', true)
        ->assertJsonPath('data.is_return_leg', false)
        ->assertJsonPath('data.price', '9000.00')
        ->assertJsonPath('data.linked_booking.is_return_leg', true)
        ->assertJsonPath('data.linked_booking.route.id', $returnRoute->id)
        ->assertJsonPath('data.linked_booking.vehicle_options.0.vehicle_option', 'back_seat')
        ->assertJsonPath('data.linked_booking.vehicle_options.0.unit_price', '11000.00');

    expect(Booking::count())->toBe(2);

    $return = Booking::where('is_return_leg', true)->firstOrFail();
    expect($return->price)->toEqual('11000.00')
        ->and($return->ev_route_id)->toBe($returnRoute->id);
});

test('round trip: a return route that is not the reverse of the outbound route surfaces as 422', function () {
    config(['booking.back_seat_enabled' => true]);

    [$outboundRoute, $outboundSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '9000.00']]);
    [$unrelatedRoute, $unrelatedSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '9000.00']]);

    $payload = bookingPayload($outboundRoute, $outboundSlot, [
        ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
    ]);
    $payload['is_round_trip'] = true;
    $payload['return_ev_route_id'] = $unrelatedRoute->id;
    $payload['return_departure_time_slot_id'] = $unrelatedSlot->id;
    $payload['return_travel_date'] = now()->addDays(3)->toDateString();
    $payload['return_selections'] = [['vehicle_option' => 'back_seat', 'passenger_count' => 1]];

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', $payload)
        ->assertStatus(422);

    expect(Booking::count())->toBe(0);
});

test('round trip: return fields are required when is_round_trip is true', function () {
    [$route, $timeSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '15000.00']]);

    $payload = bookingPayload($route, $timeSlot, [
        ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
    ]);
    $payload['is_round_trip'] = true;

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', $payload)
        ->assertStatus(422)
        ->assertJsonValidationErrors([
            'return_ev_route_id', 'return_departure_time_slot_id', 'return_travel_date', 'return_selections',
        ]);
});

test('round trip: return_travel_date before travel_date is rejected', function () {
    config(['booking.back_seat_enabled' => true]);

    [$outboundRoute, $outboundSlot] = bookableRouteAndSlot([[VehicleOption::BackSeat, '9000.00']]);
    [$returnRoute, $returnSlot] = reverseRouteAndSlot($outboundRoute, [[VehicleOption::BackSeat, '9000.00']]);

    $payload = bookingPayload($outboundRoute, $outboundSlot, [
        ['vehicle_option' => 'back_seat', 'passenger_count' => 1],
    ]);
    $payload['is_round_trip'] = true;
    $payload['return_ev_route_id'] = $returnRoute->id;
    $payload['return_departure_time_slot_id'] = $returnSlot->id;
    $payload['return_travel_date'] = now()->toDateString(); // before travel_date (addDay())
    $payload['return_selections'] = [['vehicle_option' => 'back_seat', 'passenger_count' => 1]];

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/bookings', $payload)
        ->assertStatus(422)
        ->assertJsonValidationErrors(['return_travel_date']);
});
