<?php

namespace Tests\Feature\Controllers;

use App\Events\StockSyncFinished;
use App\Facades\Settings;
use App\Jobs\BulkImportJob;
use App\Jobs\ProcessMediaUseChunkJob;
use App\Models\BulkImportLog;
use App\Models\ImportMap;
use App\VehicleSpec\Jobs\FetchVehicleSpecData;
use App\VehicleSpec\Jobs\VehicleCapIdLookup;
use Database\Seeders\Tenant\VehicleSpecSeeder;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;
use Database\Seeders\Global\CountrySeeder;
use Mockery;
use Mtc\MercuryDataModels\Finance\Jobs\FetchVehicleFinanceData;
use Mtc\MercuryDataModels\Media;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleSpecType;
use Mtc\MercuryDataModels\VehicleStandardEquipment;
use Mtc\VehicleLookup\Contracts\VehicleOptionalEquipmentItem;
use Mtc\VehicleLookup\VehicleLookupResponse;
use Mtc\VehicleLookup\VehicleLookupService;
use Tests\DatabaseTestCase;
use Tests\UserForTenant;

class VehicleControllerTest extends DatabaseTestCase
{
    use UserForTenant;

    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testIndex()
    {
        Vehicle::factory(4)->create();
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index'));

        $response->assertStatus(200);
        $this->assertIsArray($response->json('data'));
        $this->assertCount(4, $response->json('data'));
    }

    public function testIndexIncludesSoldVehiclesByDefault(): void
    {
        $available = Vehicle::factory()->create(['is_sold' => false]);
        $sold = Vehicle::factory()->create(['is_sold' => true]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index'));

        $response->assertStatus(200);
        $ids = collect($response->json('data'))->pluck('id');
        $this->assertContains($available->id, $ids);
        $this->assertContains($sold->id, $ids);
    }

    public function testIndexExcludesSoldVehiclesWhenSettingEnabled(): void
    {
        $this->enableExcludeSoldVehicles();

        $available = Vehicle::factory()->create(['is_sold' => false]);
        $sold = Vehicle::factory()->create(['is_sold' => true]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index'));

        $response->assertStatus(200);
        $ids = collect($response->json('data'))->pluck('id');
        $this->assertContains($available->id, $ids);
        $this->assertNotContains($sold->id, $ids);
    }

    public function testIndexShowsSoldVehiclesWhenSoldFilterSelectedAndSettingEnabled(): void
    {
        $this->enableExcludeSoldVehicles();

        $sold = Vehicle::factory()->create(['is_sold' => true]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index', [
                'selections' => [
                    ['type' => 'status', 'id' => 'is_sold'],
                ],
            ]));

        $response->assertStatus(200);
        $ids = collect($response->json('data'))->pluck('id');
        $this->assertContains($sold->id, $ids);
    }

    public function testIndexFiltersByHasVideo(): void
    {
        $withVideo = Vehicle::factory()->create(['main_video_url' => 'https://example.com/video.mp4']);
        $withoutVideo = Vehicle::factory()->create([
            'main_video_url' => null,
            'exterior_video_url' => null,
            'interior_video_url' => null,
        ]);

        $yesResponse = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index', [
                'selections' => [
                    ['type' => 'has_video', 'id' => 'yes'],
                ],
            ]));

        $yesResponse->assertStatus(200);
        $yesIds = collect($yesResponse->json('data'))->pluck('id');
        $this->assertContains($withVideo->id, $yesIds);
        $this->assertNotContains($withoutVideo->id, $yesIds);

        $noResponse = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index', [
                'selections' => [
                    ['type' => 'has_video', 'id' => 'no'],
                ],
            ]));

        $noResponse->assertStatus(200);
        $noIds = collect($noResponse->json('data'))->pluck('id');
        $this->assertContains($withoutVideo->id, $noIds);
        $this->assertNotContains($withVideo->id, $noIds);
    }

    public function testIndexFiltersByHasOptionalEquipment(): void
    {
        $withEquipment = Vehicle::factory()->create();
        VehicleStandardEquipment::factory()->create([
            'vehicle_id' => $withEquipment->id,
            'vehicle_type' => 'vehicle',
            'type' => VehicleSpecType::OPTIONAL_EQUIPMENT->value,
        ]);
        $withoutEquipment = Vehicle::factory()->create();

        $yesResponse = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index', [
                'selections' => [
                    ['type' => 'has_optional_equipment', 'id' => 'yes'],
                ],
            ]));

        $yesResponse->assertStatus(200);
        $yesIds = collect($yesResponse->json('data'))->pluck('id');
        $this->assertContains($withEquipment->id, $yesIds);
        $this->assertNotContains($withoutEquipment->id, $yesIds);

        $noResponse = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index', [
                'selections' => [
                    ['type' => 'has_optional_equipment', 'id' => 'no'],
                ],
            ]));

        $noResponse->assertStatus(200);
        $noIds = collect($noResponse->json('data'))->pluck('id');
        $this->assertContains($withoutEquipment->id, $noIds);
        $this->assertNotContains($withEquipment->id, $noIds);
    }

    public function testWarningsExcludeSoldVehiclesFromTotalWhenSettingEnabled(): void
    {
        $this->enableExcludeSoldVehicles();

        Vehicle::factory(2)->create(['is_sold' => false]);
        Vehicle::factory(3)->create(['is_sold' => true]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.get-warnings'));

        $response->assertStatus(200);

        $totalVehicles = collect($response->json('warnings'))
            ->firstWhere('label', __('labels.total_count'));
        $total = collect($totalVehicles['values'])->firstWhere('label', __('labels.total'));

        $this->assertSame(2, $total['value']);
    }

    private function enableExcludeSoldVehicles(): void
    {
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'Filter',
            'group' => 'Sold Vehicles',
            'name' => 'Exclude sold vehicles from listings',
            'config_key' => 'filter-results-exclude-sold-vehicles',
            'type' => 'boolean',
            'value' => true,
        ]);
    }

    public function testStore()
    {
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.store'), [
                'title' => 'foo baz bar',
            ]);

        $response->assertStatus(201);

        $this->assertEquals('foo baz bar', $response->json('data.vehicle.title'));
        $this->assertNotNull($response->json('data.vehicle.uuid'));
        $this->assertTrue(Vehicle::query()->where('title', 'foo baz bar')->exists());
        $this->assertTrue(Vehicle::query()->where('id', $response->json('data.vehicle.id'))->exists());
    }

    public function testStoreDoesNotDispatchSpecFetchWhenNoProviderEnabled()
    {
        Queue::fake();

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.store'), [
                'title' => 'no provider enabled',
            ]);

        $response->assertStatus(201);
        Queue::assertNotPushed(FetchVehicleSpecData::class);
    }

    public function testStoreDispatchesSpecFetchWhenSpecProviderEnabledAndVehicleHasMatchingIdentifier()
    {
        Queue::fake();
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'Specification / Feature Synchronization',
            'group' => 'HPI',
            'name' => 'Enabled',
            'config_key' => 'vehicle-spec-providers-hpi-enabled',
            'type' => 'boolean',
            'value' => true,
        ]);

        $lookupService = Mockery::mock(VehicleLookupService::class);
        $lookupService->shouldReceive('hasActiveDriver')->andReturn(true);
        $lookupService->shouldReceive('findActiveDriver')->andReturn('hpi');
        $lookupService->shouldReceive('findByVRM')->andReturn(
            new VehicleLookupResponse(
                registration_number: 'AB23DEV',
                make: 'Chery',
                model: 'Tiggo 7',
            )
        );
        $this->app->instance(VehicleLookupService::class, $lookupService);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.store'), [
                'registration_number' => 'AB23DEV',
            ]);

        $response->assertStatus(201);
        Queue::assertPushed(
            FetchVehicleSpecData::class,
            fn (FetchVehicleSpecData $job) => $response->json('data.vehicle.id') !== null
        );
    }

    public function testUpdateDispatchesSpecFetchWhenProviderEnabledAndVehicleHasNoEquipment()
    {
        $this->seed(CountrySeeder::class);
        Queue::fake();
        $this->enableHpiProvider();

        $vehicle = Vehicle::factory()->create([
            'cap_id' => null,
            'registration_number' => 'AB23DEV',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload());

        $response->assertStatus(200);
        Queue::assertPushed(FetchVehicleSpecData::class);
    }

    public function testUpdateDoesNotDispatchSpecFetchWhenVehicleAlreadyHasEquipment()
    {
        $this->seed(CountrySeeder::class);
        Queue::fake();
        $this->enableHpiProvider();

        $vehicle = Vehicle::factory()->create([
            'cap_id' => null,
            'registration_number' => 'AB23DEV',
        ]);
        VehicleStandardEquipment::factory()->create([
            'vehicle_id' => $vehicle->id,
            'vehicle_type' => 'vehicle',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload());

        $response->assertStatus(200);
        Queue::assertNotPushed(FetchVehicleSpecData::class);
    }

    public function testUpdateDoesNotDispatchSpecFetchWhenNoProviderEnabled()
    {
        $this->seed(CountrySeeder::class);
        Queue::fake();

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB23DEV',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload());

        $response->assertStatus(200);
        Queue::assertNotPushed(FetchVehicleSpecData::class);
    }

    public function testUpdateRejectsDuplicateEquipmentDescriptions(): void
    {
        $this->seed(CountrySeeder::class);
        Queue::fake();

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB23DEV',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload() + [
                'equipment' => [
                    ['id' => null, 'description' => 'High-End Audio System', 'category' => 'Technology'],
                    ['id' => null, 'description' => 'High-End Audio System', 'category' => 'Comfort'],
                ],
            ]);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors('equipment');
        // Validation blocks the save, so nothing is written and the unique index is never hit.
        $this->assertDatabaseMissing('vehicle_standard_equipment', [
            'vehicle_id' => $vehicle->id,
            'description' => 'High-End Audio System',
        ]);
    }

    public function testUpdateRejectsDuplicateEquipmentDescriptionsIgnoringCase(): void
    {
        $this->seed(CountrySeeder::class);
        Queue::fake();

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB23DEV',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload() + [
                'equipment' => [
                    ['id' => null, 'description' => 'High-End Audio System'],
                    ['id' => null, 'description' => 'high-end audio system'],
                ],
            ]);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors('equipment');
    }

    public function testUpdatePersistsEquipmentWithUniqueDescriptions(): void
    {
        $this->seed(CountrySeeder::class);
        Queue::fake();

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB23DEV',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload() + [
                'equipment' => [
                    ['id' => null, 'description' => 'High-End Audio System', 'category' => 'Technology'],
                    ['id' => null, 'description' => 'Heated Seats', 'category' => 'Comfort'],
                ],
            ]);

        $response->assertStatus(200);
        $this->assertDatabaseHas('vehicle_standard_equipment', [
            'vehicle_id' => $vehicle->id,
            'description' => 'High-End Audio System',
        ]);
        $this->assertDatabaseHas('vehicle_standard_equipment', [
            'vehicle_id' => $vehicle->id,
            'description' => 'Heated Seats',
        ]);
    }

    public function testUpdateDuplicateEquipmentErrorNamesTheOffendingItem(): void
    {
        $this->seed(CountrySeeder::class);
        Queue::fake();

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB23DEV',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload() + [
                'equipment' => [
                    ['id' => null, 'description' => 'High-End Audio System'],
                    ['id' => null, 'description' => 'High-End Audio System'],
                ],
            ]);

        $response->assertStatus(422);
        $this->assertStringContainsString(
            'High-End Audio System',
            $response->json('errors.equipment.0'),
        );
    }

    public function testUpdateRejectsDuplicateSpecDescriptions(): void
    {
        $this->seed(CountrySeeder::class);
        Queue::fake();

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB23DEV',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload() + [
                'specs' => [
                    ['id' => null, 'description' => 'Top Speed', 'value' => '150 mph', 'category' => 'Performance'],
                    ['id' => null, 'description' => 'Top Speed', 'value' => '240 kph', 'category' => 'Engine'],
                ],
            ]);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors('specs');
        $this->assertStringContainsString('Top Speed', $response->json('errors.specs.0'));
        // Validation blocks the save, so nothing is written and the unique index is never hit.
        $this->assertDatabaseMissing('vehicle_tech_data', [
            'vehicle_id' => $vehicle->id,
            'description' => 'Top Speed',
        ]);
    }

    public function testUpdateRejectsDuplicateSpecDescriptionsIgnoringCase(): void
    {
        $this->seed(CountrySeeder::class);
        Queue::fake();

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB23DEV',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload() + [
                'specs' => [
                    ['id' => null, 'description' => 'Top Speed', 'value' => '150 mph', 'category' => 'Performance'],
                    ['id' => null, 'description' => 'top speed', 'value' => '240 kph', 'category' => 'Engine'],
                ],
            ]);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors('specs');
    }

    public function testUpdatePersistsSpecsWithUniqueDescriptions(): void
    {
        $this->seed(CountrySeeder::class);
        Queue::fake();

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB23DEV',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload() + [
                'specs' => [
                    ['id' => null, 'description' => 'Top Speed', 'value' => '150 mph', 'category' => 'Performance'],
                    ['id' => null, 'description' => '0-60 mph', 'value' => '4.5s', 'category' => 'Performance'],
                ],
            ]);

        $response->assertStatus(200);
        $this->assertDatabaseHas('vehicle_tech_data', [
            'vehicle_id' => $vehicle->id,
            'description' => 'Top Speed',
        ]);
        $this->assertDatabaseHas('vehicle_tech_data', [
            'vehicle_id' => $vehicle->id,
            'description' => '0-60 mph',
        ]);
    }

    private function enableHpiProvider(): void
    {
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'Specification / Feature Synchronization',
            'group' => 'HPI',
            'name' => 'Enabled',
            'config_key' => 'vehicle-spec-providers-hpi-enabled',
            'type' => 'boolean',
            'value' => true,
        ]);
        Settings::update('vehicle-spec-providers-hpi-enabled', true);
    }

    private function validUpdatePayload(): array
    {
        return [
            'title' => 'lorem ipsum',
            'derivative' => 'foo baz bar',
            'registration_number' => 'AB23DEV',
            'is_published' => true,
            'make_id' => 6,
            'model_id' => 7,
            'primary_media' => null,
        ];
    }

    public function testShow()
    {
        $vehicle = Vehicle::factory()->create([
            'make_id' => 10,
            'registration_number' => 'AB23DEV',
            'title' => 'foo baz bar',
            'derivative' => 'foo-baz-bar',
        ]);
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.show', $vehicle));

        $response->assertStatus(200);

        $this->assertEquals(10, $response->json('data.vehicle.make_id'));
        $this->assertEquals('AB23 DEV', $response->json('data.vehicle.registration_number'));
        $this->assertEquals('foo baz bar', $response->json('data.vehicle.title'));
        $this->assertEquals('foo-baz-bar', $response->json('data.vehicle.derivative'));
    }

    public function testDestroy()
    {
        $offer = Vehicle::factory()->create();

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->deleteJson(route('tenant.vehicles.destroy', $offer));

        $response->assertStatus(200);
        $this->assertFalse(Vehicle::query()->where('id', $offer->id)->exists());
    }

    public function testGetFinance()
    {
        Queue::fake();
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'Finance',
            'group' => 'CodeWeavers',
            'name' => 'CodeWeavers Finance Enabled',
            'config_key' => 'finance-codeweavers-enabled',
            'min_tier' => 'standard',
            'type' => 'boolean',
            'value' => true,
        ]);

        $vehicle = Vehicle::factory()->create(['slug' => 'foo']);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.get-finance', $vehicle), [
                'term' => 60,
                'annual_mileage' => 10000,
                'deposit' => 10,
                'credit_rating' => 'excellent',
            ]);

        $response->assertStatus(200);
        Queue::assertPushed(FetchVehicleFinanceData::class);
    }

    public function testGetCapId()
    {
        $this->seed(VehicleSpecSeeder::class);
        $vehicle = Vehicle::factory()->create(['cap_id' => null]);
        $vehicle2 = Vehicle::factory()->create(['cap_id' => 'abc123']);
        Queue::fake();

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.get-cap-id', $vehicle));

        $response->assertStatus(422);

        Settings::update('vehicle-spec-providers-cap-enabled', true);
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.get-cap-id', $vehicle));
        $response->assertStatus(200);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.get-cap-id', $vehicle2));
        $response->assertStatus(422);
        Queue::assertPushed(VehicleCapIdLookup::class);
    }

    public function testUpdateStatus()
    {
        $vehicle = Vehicle::factory()->create();
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.set-status', $vehicle), [
                'state' => 'published',
            ]);

        $response->assertStatus(200);
        $vehicle->refresh();
    }

    public function testGetFeatured()
    {
        Vehicle::factory(3)->create(['featured' => true, 'is_published' => true]);
        Vehicle::factory(5)->create(['featured' => false, 'is_published' => true]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.get-featured'));
        $response->assertStatus(200);

        $this->assertIsArray($response->json('data'));
        $this->assertCount(3, $response->json('data'));
    }

    public function testSetFeatured()
    {
        $to_set_featured = Vehicle::factory(3)->create(['featured' => false, 'is_published' => true]);
        Vehicle::factory(5)->create(['featured' => false, 'is_published' => true]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.set-featured'), [
                'vehicle_ids' => $to_set_featured->pluck('id')
            ]);

        $response->assertStatus(200);

        $this->assertIsArray($response->json('data'));
        $this->assertCount(3, $response->json('data'));
        $this->assertEquals(3, Vehicle::query()->where('featured', 1)->count());

        $featured_ids = collect($response->json('data'))->pluck('id');
        $this->assertEquals($to_set_featured->pluck('id')->toArray(), $featured_ids->toArray());
    }

    public function testExport()
    {
        Vehicle::factory(10)->create();

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.export'));

        $response->assertStatus(200);
        $response->assertDownload();
    }

    public function testImport()
    {
        Queue::fake();
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.import'), [
                'type' => '',
                'file' => UploadedFile::fake()->create('vehicle-import.xlsx'),
            ]);

        $response->assertStatus(200);
        Queue::assertPushed(BulkImportJob::class);
    }

    public function testVehicleImportViaImportMap()
    {
        Queue::fake();
        Storage::fake('file-storage');
        Storage::disk('file-storage')->put(
            'foo/faz.xlsx',
            File::get(dirname(__DIR__, 2) . '/data/vehicle-import.xlsx'),
        );
        $importMap = ImportMap::query()
            ->create([
                'name' => 'foo',
                'slug' => 'foo',
                'type' => 'vehicle',
                'data' => [
                    'sub_field_delimiter' => ';',
                    'has_header_row' => true
                ],
            ]);

        $importMap->fields()->create([
            'on_file' => 'is_published',
            'on_model' => 'is_published',
        ]);
        $importMap->fields()->create([
            'on_file' => 'registration_number',
            'on_model' => 'registration_number',
        ]);
        $importMap->fields()->create([
            'on_file' => 'slug',
            'on_model' => 'slug',
        ]);
        $importMap->fields()->create([
            'on_file' => 'features',
            'on_model' => 'features',
        ]);

        $log = BulkImportLog::query()
            ->create([
                'user_id' => 1,
                'object_type' => 'vehicle',
                'import_type' => 1,
                'automations' => [],
                'filename' => 'foo/faz.xlsx',
            ]);

        new BulkImportJob('vehicle', 'foo/faz.xlsx', 'edit-vehicles', 1)
            ->setLog($log)
            ->setAdditionalTasks([])
            ->handle();

        $this->assertEquals(3, Vehicle::query()->count());
        $vehicle = Vehicle::query()
            ->with('features')
            ->where('registration_number', 'MBW6 36N')
            ->firstOrFail();
        $this->assertEquals(3, $vehicle?->features->count());
    }

    public function testVehicleBulkImportDispatchesStockSyncFinished()
    {
        Queue::fake();
        Event::fake([StockSyncFinished::class]);
        Storage::fake('file-storage');
        Storage::disk('file-storage')->put(
            'foo/faz.xlsx',
            File::get(dirname(__DIR__, 2) . '/data/vehicle-import.xlsx'),
        );
        $importMap = ImportMap::query()
            ->create([
                'name' => 'foo',
                'slug' => 'foo',
                'type' => 'vehicle',
                'data' => [
                    'sub_field_delimiter' => ';',
                    'has_header_row' => true,
                ],
            ]);
        $importMap->fields()->create([
            'on_file' => 'registration_number',
            'on_model' => 'registration_number',
        ]);

        $log = BulkImportLog::query()
            ->create([
                'user_id' => 1,
                'object_type' => 'vehicle',
                'import_type' => $importMap->id,
                'automations' => [],
                'filename' => 'foo/faz.xlsx',
            ]);

        new BulkImportJob('vehicle', 'foo/faz.xlsx', 'edit-vehicles', (string) $importMap->id)
            ->setLog($log)
            ->handle();

        Event::assertDispatched(
            StockSyncFinished::class,
            fn (StockSyncFinished $event) => $event->provider === 'import-map-' . $importMap->id,
        );
    }

    public function testCopy()
    {
        $vehicle = Vehicle::factory()->create(['title' => 'foo']);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.copy', $vehicle->id));

        $response->assertStatus(201);
        $this->assertTrue(Vehicle::query()->where('id', $response->json('data.vehicle.id'))->exists());
        $this->assertNotEquals($vehicle->id, $response->json('data.vehicle.id'));
        $this->assertEquals($vehicle->title, $response->json('data.vehicle.title'));
    }

    public function testMediaRecrop()
    {
        $this->seed(VehicleSpecSeeder::class);

        Queue::fake();

        $vehicle = Vehicle::factory()->create();
        $media = Media::factory(3)->create();
        foreach ($media as $medium) {
            $vehicle->mediaUses()->create(['media_id' => $medium->id]);
        }

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.media-recrop', $vehicle));

        $response->assertStatus(200);
        Queue::assertPushed(ProcessMediaUseChunkJob::class);
    }

    public function testOptionalExtrasReturnsExtrasWhenDriverSupports(): void
    {
        $lookupService = Mockery::mock(VehicleLookupService::class);
        $lookupService->shouldReceive('supportsOptionalExtras')->andReturn(true);
        $lookupService->shouldReceive('findOptionalExtras')
            ->with('AB23DEV')
            ->andReturn([
                new VehicleOptionalEquipmentItem('ALY', 'Alloy Wheels', 'Wheels', 600.0, 120.0),
                new VehicleOptionalEquipmentItem('PAN', 'Panoramic Sunroof', 'Comfort', 1200.0, 240.0),
            ]);
        $this->app->instance(VehicleLookupService::class, $lookupService);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson('/api/vehicle-lookup/extras', [
                'registration_number' => 'AB23DEV',
            ]);

        $response->assertStatus(200);
        $response->assertJson([
            'supported' => true,
            'extras' => [
                ['code' => 'ALY', 'description' => 'Alloy Wheels', 'category' => 'Wheels', 'price' => 600, 'vat' => 120],
                ['code' => 'PAN', 'description' => 'Panoramic Sunroof', 'category' => 'Comfort', 'price' => 1200, 'vat' => 240],
            ],
        ]);
    }

    public function testOptionalExtrasReturnsUnsupportedWhenDriverDoesNotSupport(): void
    {
        $lookupService = Mockery::mock(VehicleLookupService::class);
        $lookupService->shouldReceive('supportsOptionalExtras')->andReturn(false);
        $lookupService->shouldNotReceive('findOptionalExtras');
        $this->app->instance(VehicleLookupService::class, $lookupService);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson('/api/vehicle-lookup/extras', [
                'registration_number' => 'AB23DEV',
            ]);

        $response->assertStatus(200);
        $response->assertExactJson(['supported' => false, 'extras' => []]);
    }

    public function testOptionalExtrasRequiresRegistrationNumberOrDerivativeId(): void
    {
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson('/api/vehicle-lookup/extras', []);

        $response->assertStatus(422);
    }

    public function testOptionalExtrasAcceptsDerivativeId(): void
    {
        $lookupService = Mockery::mock(VehicleLookupService::class);
        $lookupService->shouldReceive('supportsOptionalExtras')->andReturn(true);
        $lookupService->shouldNotReceive('findOptionalExtras');
        $lookupService->shouldReceive('findOptionalExtrasForDerivative')
            ->with('DER-789', '2022-04-10')
            ->andReturn([
                new VehicleOptionalEquipmentItem('', 'Heated Seats', 'Comfort', 350.0, 0.0),
            ]);
        $this->app->instance(VehicleLookupService::class, $lookupService);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson('/api/vehicle-lookup/extras', [
                'derivative_id' => 'DER-789',
                'effective_date' => '2022-04-10',
            ]);

        $response->assertStatus(200);
        $response->assertJson([
            'supported' => true,
            'extras' => [
                ['description' => 'Heated Seats', 'category' => 'Comfort', 'price' => 350],
            ],
        ]);
    }

    public function testStorePersistsOptionalExtrasWhenCreatingFromLookup(): void
    {
        Queue::fake();
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'Specification / Feature Synchronization',
            'group' => 'HPI',
            'name' => 'Enabled',
            'config_key' => 'vehicle-spec-providers-hpi-enabled',
            'type' => 'boolean',
            'value' => true,
        ]);

        $lookupService = Mockery::mock(VehicleLookupService::class);
        $lookupService->shouldReceive('hasActiveDriver')->andReturn(true);
        $lookupService->shouldReceive('findActiveDriver')->andReturn('hpi');
        $lookupService->shouldReceive('findByVRM')->andReturn(
            new VehicleLookupResponse(
                registration_number: 'AB23DEV',
                make: 'Chery',
                model: 'Tiggo 7',
            )
        );
        $this->app->instance(VehicleLookupService::class, $lookupService);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.store'), [
                'registration_number' => 'AB23DEV',
                'optional_extras' => [
                    [
                        'code' => 'ALY',
                        'description' => '18" Alloy Wheels',
                        'category' => 'Wheels',
                        'price' => 600,
                        'vat' => 120,
                    ],
                    [
                        'code' => 'PAN',
                        'description' => 'Panoramic Sunroof',
                        'category' => 'Comfort',
                        'price' => 1200,
                        'vat' => 240,
                    ],
                ],
            ]);

        $response->assertStatus(201);
        $vehicleId = $response->json('data.vehicle.id');

        $this->assertDatabaseHas('vehicle_standard_equipment', [
            'vehicle_id' => $vehicleId,
            'type' => 'Optional',
            'code' => 'ALY',
            'description' => '18" Alloy Wheels',
            'category' => 'Wheels',
            'price' => 600,
            'vat_amount' => 120,
        ]);
        $this->assertDatabaseHas('vehicle_standard_equipment', [
            'vehicle_id' => $vehicleId,
            'type' => 'Optional',
            'code' => 'PAN',
            'description' => 'Panoramic Sunroof',
        ]);
    }

    public function testStorePersistsOptionalExtrasOnManualBuild(): void
    {
        Queue::fake();

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.store'), [
                'title' => 'manual build',
                'optional_extras' => [
                    ['code' => '', 'description' => 'Heated Steering Wheel', 'category' => 'Comfort', 'price' => 150],
                ],
            ]);

        $response->assertStatus(201);
        $this->assertDatabaseHas('vehicle_standard_equipment', [
            'vehicle_id' => $response->json('data.vehicle.id'),
            'type' => 'Optional',
            'description' => 'Heated Steering Wheel',
            'category' => 'Comfort',
            'price' => 150,
        ]);
    }

    public function testStoreRejectsDuplicateRegistrationNumberByDefault()
    {
        Vehicle::factory()->create(['registration_number' => 'AB23DEV']);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.store'), [
                'registration_number' => 'AB23DEV',
            ]);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors('registration_number');
        $this->assertSame(1, Vehicle::query()->count());
    }

    public function testStoreAllowsDuplicateRegistrationNumberWhenUniquenessDisabled()
    {
        $this->disableRegistrationUniqueness();
        Vehicle::factory()->create(['registration_number' => 'AB23DEV']);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicles.store'), [
                'registration_number' => 'AB23DEV',
            ]);

        $response->assertStatus(201);
        $this->assertSame(2, Vehicle::query()->count());
    }

    public function testUpdateAllowsDuplicateRegistrationNumberWhenUniquenessDisabled()
    {
        $this->seed(CountrySeeder::class);
        $this->disableRegistrationUniqueness();
        Vehicle::factory()->create(['registration_number' => 'AB23 DEV']);
        $vehicle = Vehicle::factory()->create(['registration_number' => 'CD34 EFG']);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $vehicle), $this->validUpdatePayload());

        $response->assertStatus(200);
        $this->assertSame('AB23 DEV', $vehicle->fresh()->registration_number);
    }

    private function disableRegistrationUniqueness(): void
    {
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'General',
            'group' => 'Vehicles',
            'name' => 'Require unique registration numbers',
            'config_key' => 'vehicle-registration-number-unique',
            'type' => 'boolean',
            'value' => false,
        ]);
        Settings::update('vehicle-registration-number-unique', false);
    }
}
