<?php

use App\Models\User;
use Modules\Catalog\Models\DepartureTimeSlot;
use Modules\Catalog\Models\Destination;
use Modules\Catalog\Models\EvCompany;
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;
});

test('searches active routes with nested company, destinations, time slots and pricing', function () {
    $route = EvRoute::factory()->create(['is_active' => true]);
    EvRoute::factory()->create(['is_active' => false]);

    $slot = DepartureTimeSlot::factory()->create();
    $route->timeSlots()->attach($slot->id, ['is_active' => true]);

    RoutePricing::factory()->create([
        'ev_route_id' => $route->id,
        'vehicle_option' => VehicleOption::FrontSeat,
        'price' => 12000,
    ]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/routes/search')
        ->assertSuccessful()
        ->assertJsonCount(1, 'routes.data')
        ->assertJsonPath('routes.data.0.id', $route->id)
        ->assertJsonPath('routes.data.0.company.id', $route->ev_company_id)
        ->assertJsonPath('routes.data.0.from_destination.id', $route->from_destination_id)
        ->assertJsonPath('routes.data.0.to_destination.id', $route->to_destination_id)
        ->assertJsonPath('routes.data.0.time_slots.0.id', $slot->id)
        ->assertJsonPath('routes.data.0.time_slots.0.is_active', true)
        ->assertJsonPath('routes.data.0.pricing.0.vehicle_option', 'front_seat')
        ->assertJsonPath('routes.data.0.pricing.0.price', '12000.00')
        ->assertJsonPath('routes.data.0.pricing.0.max_per_booking', config('booking.front_seat_max_per_booking'))
        ->assertJsonCount(0, 'return_routes.data');
});

test('filters routes by company, from, and to', function () {
    $companyA = EvCompany::factory()->create();
    $companyB = EvCompany::factory()->create();
    $yangon = Destination::factory()->create();
    $mandalay = Destination::factory()->create();
    $bagan = Destination::factory()->create();

    $matching = EvRoute::factory()->create([
        'ev_company_id' => $companyA->id,
        'from_destination_id' => $yangon->id,
        'to_destination_id' => $mandalay->id,
        'is_active' => true,
    ]);

    EvRoute::factory()->create([
        'ev_company_id' => $companyB->id,
        'from_destination_id' => $yangon->id,
        'to_destination_id' => $mandalay->id,
        'is_active' => true,
    ]);

    EvRoute::factory()->create([
        'ev_company_id' => $companyA->id,
        'from_destination_id' => $yangon->id,
        'to_destination_id' => $bagan->id,
        'is_active' => true,
    ]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/routes/search', [
            'company' => $companyA->id,
            'from' => $yangon->id,
            'to' => $mandalay->id,
        ])
        ->assertSuccessful()
        ->assertJsonCount(1, 'routes.data')
        ->assertJsonPath('routes.data.0.id', $matching->id);
});

test('filters routes by time_slot', function () {
    $morning = DepartureTimeSlot::factory()->create(['time' => '06:00']);
    $evening = DepartureTimeSlot::factory()->create(['time' => '18:00']);

    $morningRoute = EvRoute::factory()->create(['is_active' => true]);
    $morningRoute->timeSlots()->attach($morning->id, ['is_active' => true]);

    $eveningRoute = EvRoute::factory()->create(['is_active' => true]);
    $eveningRoute->timeSlots()->attach($evening->id, ['is_active' => true]);

    // Attached but inactive on this route — must not match.
    $inactivePivotRoute = EvRoute::factory()->create(['is_active' => true]);
    $inactivePivotRoute->timeSlots()->attach($morning->id, ['is_active' => false]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/routes/search', ['time_slot' => '06:00'])
        ->assertSuccessful()
        ->assertJsonCount(1, 'routes.data')
        ->assertJsonPath('routes.data.0.id', $morningRoute->id);
});

test('round trip: returns both routes and return_routes, swapped from/to', function () {
    $company = EvCompany::factory()->create();
    $yangon = Destination::factory()->create();
    $mandalay = Destination::factory()->create();

    $outbound = EvRoute::factory()->create([
        'ev_company_id' => $company->id,
        'from_destination_id' => $yangon->id,
        'to_destination_id' => $mandalay->id,
        'is_active' => true,
    ]);
    $return = EvRoute::factory()->create([
        'ev_company_id' => $company->id,
        'from_destination_id' => $mandalay->id,
        'to_destination_id' => $yangon->id,
        'is_active' => true,
    ]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/routes/search', [
            'round_trip' => true,
            'from' => $yangon->id,
            'to' => $mandalay->id,
        ])
        ->assertSuccessful()
        ->assertJsonCount(1, 'routes.data')
        ->assertJsonPath('routes.data.0.id', $outbound->id)
        ->assertJsonCount(1, 'return_routes.data')
        ->assertJsonPath('return_routes.data.0.id', $return->id);
});

test('response includes the distinct companies and time_slots actually available for the from-to pair', function () {
    $yangon = Destination::factory()->create();
    $mandalay = Destination::factory()->create();

    $companyA = EvCompany::factory()->create(['name' => 'Alpha EV']);
    $companyB = EvCompany::factory()->create(['name' => 'Beta EV']);
    $morning = DepartureTimeSlot::factory()->create(['time' => '06:00']);
    $evening = DepartureTimeSlot::factory()->create(['time' => '18:00']);
    $inactiveSlot = DepartureTimeSlot::factory()->create(['time' => '12:00']);

    $routeA = EvRoute::factory()->create([
        'ev_company_id' => $companyA->id,
        'from_destination_id' => $yangon->id,
        'to_destination_id' => $mandalay->id,
        'is_active' => true,
    ]);
    $routeA->timeSlots()->attach([$morning->id => ['is_active' => true], $inactiveSlot->id => ['is_active' => false]]);

    $routeB = EvRoute::factory()->create([
        'ev_company_id' => $companyB->id,
        'from_destination_id' => $yangon->id,
        'to_destination_id' => $mandalay->id,
        'is_active' => true,
    ]);
    $routeB->timeSlots()->attach($evening->id, ['is_active' => true]);

    // Unrelated pair — must not leak into the facets.
    $bagan = Destination::factory()->create();
    $unrelated = EvRoute::factory()->create([
        'from_destination_id' => $yangon->id,
        'to_destination_id' => $bagan->id,
        'is_active' => true,
    ]);
    $unrelated->timeSlots()->attach(DepartureTimeSlot::factory()->create(['time' => '09:00'])->id, ['is_active' => true]);

    // Applying a company filter narrows `routes.data` but must not narrow
    // the facets themselves — facets always reflect the full from-to pair.
    $response = $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/routes/search', [
            'from' => $yangon->id,
            'to' => $mandalay->id,
            'company' => $companyA->id,
        ])
        ->assertSuccessful()
        ->assertJsonCount(1, 'routes.data')
        ->json();

    expect(collect($response['filters']['companies'])->pluck('id')->sort()->values()->all())
        ->toBe([$companyA->id, $companyB->id]);
    // Facet shape is trimmed to id/name/mm_name — not the full company resource.
    expect(array_keys($response['filters']['companies'][0]))->toBe(['id', 'name', 'mm_name']);
    expect(collect($response['filters']['time_slots'])->pluck('time')->all())
        ->toBe(['06:00', '18:00']); // sorted by time, inactive pivot and unrelated pair excluded
});

test('filter options are empty when from/to are not both given', function () {
    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/routes/search')
        ->assertSuccessful()
        ->assertJson(['filters' => ['companies' => [], 'time_slots' => []]]);
});

test('round trip: return_filters reflect the swapped to-from pair', function () {
    $yangon = Destination::factory()->create();
    $mandalay = Destination::factory()->create();
    $returnCompany = EvCompany::factory()->create();

    EvRoute::factory()->create([
        'from_destination_id' => $yangon->id,
        'to_destination_id' => $mandalay->id,
        'is_active' => true,
    ]);
    EvRoute::factory()->create([
        'ev_company_id' => $returnCompany->id,
        'from_destination_id' => $mandalay->id,
        'to_destination_id' => $yangon->id,
        'is_active' => true,
    ]);

    $response = $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/routes/search', [
            'round_trip' => true,
            'from' => $yangon->id,
            'to' => $mandalay->id,
        ])
        ->assertSuccessful()
        ->json();

    expect(collect($response['return_filters']['companies'])->pluck('id')->all())
        ->toBe([$returnCompany->id]);
});

test('round trip: routes and return_routes paginate independently via page and return_page', function () {
    $company = EvCompany::factory()->create();
    $yangon = Destination::factory()->create();
    $mandalay = Destination::factory()->create();

    // 20 outbound routes (2 pages of 15), only 3 return routes (1 page).
    EvRoute::factory()->count(20)->create([
        'ev_company_id' => $company->id,
        'from_destination_id' => $yangon->id,
        'to_destination_id' => $mandalay->id,
        'is_active' => true,
    ]);
    EvRoute::factory()->count(3)->create([
        'ev_company_id' => $company->id,
        'from_destination_id' => $mandalay->id,
        'to_destination_id' => $yangon->id,
        'is_active' => true,
    ]);

    // page=2 must give the 2nd page of routes (5 remaining), while
    // return_routes — with no return_page given — must still return its own
    // full page 1 (all 3), not an empty slice at offset 2.
    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/routes/search', [
            'round_trip' => true,
            'from' => $yangon->id,
            'to' => $mandalay->id,
            'page' => 2,
        ])
        ->assertSuccessful()
        ->assertJsonCount(5, 'routes.data')
        ->assertJsonPath('routes.meta.current_page', 2)
        ->assertJsonCount(3, 'return_routes.data')
        ->assertJsonPath('return_routes.meta.current_page', 1);
});

test('round_trip without from and to is rejected', function () {
    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/routes/search', ['round_trip' => true])
        ->assertStatus(422)
        ->assertJsonValidationErrors(['from', 'to']);
});

test('paginates routes', function () {
    EvRoute::factory()->count(20)->create(['is_active' => true]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->postJson('/api/v1/routes/search')
        ->assertSuccessful()
        ->assertJsonCount(15, 'routes.data')
        ->assertJsonPath('routes.meta.total', 20);
});

test('shows a single active route', function () {
    $route = EvRoute::factory()->create(['is_active' => true]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->getJson("/api/v1/routes/{$route->id}")
        ->assertSuccessful()
        ->assertJsonPath('data.id', $route->id);
});

test('an inactive route is not found via show, pricing, or time-slots', function () {
    $route = EvRoute::factory()->create(['is_active' => false]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->getJson("/api/v1/routes/{$route->id}")
        ->assertNotFound();

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->getJson("/api/v1/routes/{$route->id}/pricing")
        ->assertNotFound();

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->getJson("/api/v1/routes/{$route->id}/time-slots")
        ->assertNotFound();
});

test('lists a route\'s pricing including blocked options', function () {
    $route = EvRoute::factory()->create(['is_active' => true]);

    RoutePricing::factory()->create(['ev_route_id' => $route->id, 'vehicle_option' => VehicleOption::FrontSeat, 'price' => 12000]);
    RoutePricing::factory()->create(['ev_route_id' => $route->id, 'vehicle_option' => VehicleOption::WholeVehicle, 'is_blocked' => true]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->getJson("/api/v1/routes/{$route->id}/pricing")
        ->assertSuccessful()
        ->assertJsonCount(2, 'data')
        ->assertJsonFragment([
            'vehicle_option' => 'front_seat',
            'price' => '12000.00',
            'is_blocked' => false,
            'max_per_booking' => config('booking.front_seat_max_per_booking'),
        ])
        ->assertJsonFragment([
            'vehicle_option' => 'whole_vehicle',
            'is_blocked' => true,
            'max_per_booking' => config('booking.whole_vehicle_max_per_booking'),
        ]);
});

test('lists a route\'s time slots with the pivot active flag', function () {
    $route = EvRoute::factory()->create(['is_active' => true]);
    $active = DepartureTimeSlot::factory()->create();
    $inactive = DepartureTimeSlot::factory()->create();

    $route->timeSlots()->attach([
        $active->id => ['is_active' => true],
        $inactive->id => ['is_active' => false],
    ]);

    $this->withHeader('Authorization', "Bearer {$this->token}")
        ->getJson("/api/v1/routes/{$route->id}/time-slots")
        ->assertSuccessful()
        ->assertJsonCount(2, 'data')
        ->assertJsonFragment(['id' => $active->id, 'is_active' => true])
        ->assertJsonFragment(['id' => $inactive->id, 'is_active' => false]);
});

test('routes endpoints reject unauthenticated requests', function () {
    $route = EvRoute::factory()->create(['is_active' => true]);

    $this->postJson('/api/v1/routes/search')->assertUnauthorized();
    $this->getJson("/api/v1/routes/{$route->id}")->assertUnauthorized();
    $this->getJson("/api/v1/routes/{$route->id}/pricing")->assertUnauthorized();
    $this->getJson("/api/v1/routes/{$route->id}/time-slots")->assertUnauthorized();
});
