<?php

namespace Tests\Feature\Controllers;

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\Bus;
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\VehicleStandardEquipment;
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 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);
    }

    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 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);
    }
}
