<?php

namespace Tests\Tenant;

use App\Console\Commands\RecreateVehicleSlugs;
use App\Facades\Settings;
use Carbon\Carbon;
use App\VehicleRepository;
use Database\Seeders\Tenant\AutomotiveSettingSeeder;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Bus;
use Mtc\ContentManager\Facades\Media;
use Mtc\ContentManager\Models\Media as MediaModel;
use Mtc\MercuryDataModels\BodyStyleType;
use Mtc\MercuryDataModels\Enquiry;
use Mtc\MercuryDataModels\FuelType;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleMake;
use Mtc\MercuryDataModels\VehicleModel;
use Mtc\MercuryDataModels\VehiclePriceHistory;
use Mtc\MercuryDataModels\ResourceView;
use Tests\TenantTestCase;

class VehicleTest extends TenantTestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testVehicleHasImages()
    {
        Bus::fake();
        $this->seed(AutomotiveSettingSeeder::class);

        $vehicle = Vehicle::factory()->create();
        $media = MediaModel::factory(3)->create();

        Media::setUsesForModel($media->pluck('id')->toArray(), $vehicle);

        $this->assertEquals(3, $vehicle->mediaUses()->count());
    }


    public function testVehicleSlug()
    {
        $this->seed(AutomotiveSettingSeeder::class);
        Settings::update('automotive-vehicles-url_building_format', 'vrm-make-model');
        $make = VehicleMake::factory()->create(['name' => 'BMW']);
        $model = VehicleModel::factory()->create(['name' => 'i3']);
        $vehicle = Vehicle::factory()->make([
            'make_id' => $make->id,
            'model_id' => $model->id,
            'registration_number' => 'foo',
        ]);
        $vehicle->slug = null;
        $vehicle->save();

        $this->assertEquals('foo-bmw-i3', $vehicle->slug);

        Settings::update('automotive-vehicles-url_building_format', 'title');
        $vehicle2 = Vehicle::factory()->make(['title' => 'Hello my Car']);
        $vehicle2->slug = null;
        $vehicle2->save();
        $this->assertEquals('hello-my-car', $vehicle2->slug);

        Settings::update('automotive-vehicles-url_building_format', 'uuid');
        $vehicle3 = Vehicle::factory()->make(['uuid' => 'baz']);
        $vehicle3->slug = null;
        $vehicle3->save();
        $this->assertEquals('baz', $vehicle3->slug);

        Settings::update('automotive-vehicles-url_building_format', 'make-model-derivative');
        $vehicle4 = Vehicle::factory()->make([
            'make_id' => $make->id,
            'model_id' => $model->id,
            'derivative' => 'M Sport Auto',
        ]);
        $vehicle4->slug = null;
        $vehicle4->save();
        $this->assertEquals('bmw-i3-m-sport-auto', $vehicle4->slug);
    }

    public function testMakeModelDerivativeSlugAppendsFiveCharSuffixOnCollision()
    {
        $this->seed(AutomotiveSettingSeeder::class);
        Settings::update('automotive-vehicles-url_building_format', 'make-model-derivative');
        $make = VehicleMake::factory()->create(['name' => 'BMW']);
        $model = VehicleModel::factory()->create(['name' => 'i3']);

        $attributes = [
            'make_id' => $make->id,
            'model_id' => $model->id,
            'derivative' => 'M Sport Auto',
        ];

        $first = Vehicle::factory()->make($attributes);
        $first->slug = null;
        $first->save();
        $this->assertEquals('bmw-i3-m-sport-auto', $first->slug);

        $second = Vehicle::factory()->make($attributes);
        $second->slug = null;
        $second->save();

        // Collision triggers a 5-character lowercase alphanumeric suffix
        $this->assertMatchesRegularExpression('/^bmw-i3-m-sport-auto-[a-z0-9]{5}$/', $second->slug);
    }

    public function testSearchContentSetWhenSaving()
    {
        $make = VehicleMake::factory()->create(['name' => 'bmw']);
        $model = VehicleModel::factory()->create(['name' => '330i']);
        $fuelType = FuelType::factory()->create(['name' => 'Petrol']);
        $bodyStyle = BodyStyleType::factory()->create(['name' => 'Coupe']);
        $vehicle = Vehicle::factory()->create([
            'make_id' => $make->id,
            'model_id' => $model->id,
            'fuel_type_id' => $fuelType->id,
            'body_style_id' => $bodyStyle->id,
            'derivative' => 'e92 Petrol Coupe',
            'registration_number' => 'AC69 DDE',
            'description' => '<p> hello world</p>',
            'colour' => 'black',
            'manufacture_year' => 2007,
        ]);

        $this->assertStringContainsString('bmw', $vehicle->search_content);
        $this->assertStringContainsString('330i', $vehicle->search_content);
        $this->assertStringContainsString('petrol', $vehicle->search_content);
        $this->assertStringContainsString('e92', $vehicle->search_content);
        $this->assertStringContainsString('AC69', $vehicle->search_content);
    }

    public function testOriginalPriceUpdatesOnPriceChange()
    {
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'type' => 'bool',
            'value' => true,
            'config_key' => 'vehicles-price-change-updates-original-price'
        ]);

        $vehicle = Vehicle::factory()->create(['price' => 123, 'original_price' => null]);

        $this->assertDatabaseCount(VehiclePriceHistory::class, 1);
        $this->assertNull($vehicle->original_price);
        $vehicle->update(['price' => 200]);
        $this->assertNull($vehicle->original_price);
        $this->assertDatabaseCount(VehiclePriceHistory::class, 2);
        $vehicle->update(['price' => 140]);
        $this->assertEquals(200, $vehicle->original_price);
        $this->assertDatabaseCount(VehiclePriceHistory::class, 3);
        $vehicle->update(['price' => 120]);
        $this->assertEquals(200, $vehicle->original_price);
        $this->assertDatabaseCount(VehiclePriceHistory::class, 4);
        $vehicle->update(['price' => '120']);
        $this->assertEquals(200, $vehicle->original_price);
        $this->assertDatabaseCount(VehiclePriceHistory::class, 4);
        $vehicle->update(['price' => 120]);
        $this->assertEquals(200, $vehicle->original_price);
        $this->assertDatabaseCount(VehiclePriceHistory::class, 4);

        $vehicle->update(['price' => 200]);
        $this->assertNull($vehicle->original_price);
        $this->assertDatabaseCount(VehiclePriceHistory::class, 5);
    }

    public function testUniqueSlug()
    {
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'auto-update-vehicle-slug',
            'value' => 'boolean',
            'type' => true,

        ]);

        // set initial vehicle with slug foo
        Vehicle::factory()->create([
            'title' => 'foo',
        ]);

        $this->assertCount(1, Vehicle::query()->where('slug', 'foo')->get());


        // create with duplicate slug
        $vehicle_one = Vehicle::factory()->create([
            'title' => 'foo',
        ]);

        // assert that new vehicle got a different slug
        $this->assertNotEquals('foo', Vehicle::query()->find($vehicle_one->id)->slug);

        // save with duplicate slug
        $vehicle_two = new Vehicle();
        $vehicle_two->title = 'foo';
        $vehicle_two->slug = 'foo';
        $vehicle_two->save();

        // assert that new vehicle got a different slug
        $this->assertNotEquals('foo', Vehicle::query()->find($vehicle_two->id)->slug);

        // update to duplicate slug
        $vehicle_three = Vehicle::factory()->create([
            'title' => 'foo',
        ]);
        $vehicle_three->update([
            'slug' => 'foo',
        ]);

        // assert that updated vehicle got a different slug
        $this->assertNotEquals('foo', Vehicle::query()->find($vehicle_three->id)->slug);

        $this->assertCount(1, Vehicle::query()->where('slug', 'foo')->get());
    }

    public function testRecreateSlugs()
    {
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'auto-update-vehicle-slug',
            'value' => 'boolean',
            'type' => true,

        ]);

        Vehicle::factory(5)->create([
            'title' => 'foo',
        ]);

        // Update vehicle slugs without triggering observer functions.
        // This should force a scenario with duplicate slugs.
        Vehicle::query()->get()->each(function ($vehicle) {
            $vehicle->slug = 'foo';
            $vehicle->saveQuietly();
        });

        // assert that we have duplicate slugs
        $this->assertCount(5, Vehicle::query()->where('slug', 'foo')->get());

        new RecreateVehicleSlugs()->handle(App::make(VehicleRepository::class));

        // assert that we have 5 vehicles and that original slug is not duplicated
        $this->assertCount(5, Vehicle::query()->get());
        $this->assertCount(1, Vehicle::query()->where('slug', 'foo')->get());
    }

    public function testNewVehicleGetsCleanSlugWhenNoCollision()
    {
        $this->seed(AutomotiveSettingSeeder::class);
        Settings::update('automotive-vehicles-url_building_format', 'vrm-make-model');

        $make = VehicleMake::factory()->create(['name' => 'BMW']);
        $model = VehicleModel::factory()->create(['name' => 'i3']);

        $vehicle = Vehicle::factory()->make([
            'make_id' => $make->id,
            'model_id' => $model->id,
            'registration_number' => 'foo',
        ]);
        $vehicle->slug = null;
        $vehicle->save();

        $this->assertEquals('foo-bmw-i3', $vehicle->slug);
    }

    public function testNewVehicleGetsRandomSuffixWhenCollidingWithExisting()
    {
        $this->seed(AutomotiveSettingSeeder::class);
        Settings::update('automotive-vehicles-url_building_format', 'title');

        // Factory populates slug by default, which would skip the observer's
        // generation in the creating() hook — null it to exercise build logic.
        Vehicle::factory()->create(['title' => 'foo', 'slug' => null]);

        $second = Vehicle::factory()->create(['title' => 'foo', 'slug' => null]);

        $this->assertStringStartsWith('foo-', $second->slug);
        $this->assertNotEquals('foo', $second->slug);
        $this->assertCount(1, Vehicle::query()->where('slug', 'foo')->get());
    }

    public function testExistingVehicleResaveDoesNotSelfCollide()
    {
        // Regression: the collision check used to match the vehicle's own row,
        // which appended a random suffix on every observer rebuild even when no
        // other vehicle held the same slug.
        $this->seed(AutomotiveSettingSeeder::class);
        Settings::update('automotive-vehicles-url_building_format', 'vrm-make-model');
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'auto-update-vehicle-slug',
            'value' => 'boolean',
            'type' => true,
        ]);

        $make = VehicleMake::factory()->create(['name' => 'BMW']);
        $model = VehicleModel::factory()->create(['name' => 'i3']);

        $vehicle = Vehicle::factory()->create([
            'make_id' => $make->id,
            'model_id' => $model->id,
            'registration_number' => 'foo',
            'title' => 'Original Title',
        ]);

        $this->assertEquals('foo-bmw-i3', $vehicle->slug);

        // Dirty a tracked field so shouldUpdateSlug returns true and the
        // observer re-runs createUniqueSlug. The rebuilt slug equals the
        // current one — without the self-exclusion fix this would append
        // a random suffix.
        $vehicle->update(['title' => 'Different Title']);

        $this->assertEquals('foo-bmw-i3', $vehicle->fresh()->slug);
    }

    public function testExistingVehicleGetsSuffixWhenRebuildCollidesWithAnotherVehicle()
    {
        $this->seed(AutomotiveSettingSeeder::class);
        Settings::update('automotive-vehicles-url_building_format', 'vrm-make-model');
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'auto-update-vehicle-slug',
            'value' => 'boolean',
            'type' => true,
        ]);

        $make = VehicleMake::factory()->create(['name' => 'BMW']);
        $model = VehicleModel::factory()->create(['name' => 'i3']);

        // First vehicle takes the clean slug.
        Vehicle::factory()->create([
            'make_id' => $make->id,
            'model_id' => $model->id,
            'registration_number' => 'foo',
        ]);

        // Second vehicle starts with a different reg, then gets updated so its
        // rebuilt slug would collide with the first vehicle.
        $other = Vehicle::factory()->create([
            'make_id' => $make->id,
            'model_id' => $model->id,
            'registration_number' => 'bar',
        ]);
        $this->assertEquals('bar-bmw-i3', $other->slug);

        $other->update(['registration_number' => 'foo']);

        $this->assertNotEquals('foo-bmw-i3', $other->fresh()->slug);
        $this->assertStringStartsWith('foo-bmw-i3-', $other->fresh()->slug);
        $this->assertCount(1, Vehicle::query()->where('slug', 'foo-bmw-i3')->get());
    }

    public function testSlugStrategyChangeRegeneratesSlugCleanly()
    {
        $this->seed(AutomotiveSettingSeeder::class);
        Settings::update('automotive-vehicles-url_building_format', 'vrm-make-model');
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'auto-update-vehicle-slug',
            'value' => 'boolean',
            'type' => true,
        ]);

        $make = VehicleMake::factory()->create(['name' => 'BMW']);
        $model = VehicleModel::factory()->create(['name' => 'i3']);

        $vehicle = Vehicle::factory()->create([
            'make_id' => $make->id,
            'model_id' => $model->id,
            'registration_number' => 'foo',
            'uuid' => 'abc-def-ghi',
            'title' => 'Original Title',
            'slug' => null,
        ]);
        $this->assertEquals('foo-bmw-i3', $vehicle->slug);

        // Switch the strategy. The next save with a dirty tracked field will
        // rebuild the slug via the saving observer, mirroring the user-
        // reported scenario.
        Settings::update('automotive-vehicles-url_building_format', 'uuid');
        $vehicle->update(['title' => 'Updated Title']);

        $this->assertEquals('abc-def-ghi', $vehicle->fresh()->slug);

        // Triggering another rebuild whose build output equals the current
        // slug must not introduce a random suffix by colliding with the
        // vehicle's own row.
        $vehicle->update(['title' => 'Updated Title Again']);

        $this->assertEquals('abc-def-ghi', $vehicle->fresh()->slug);
    }

    public function testSlugStrategyChangeAddsSuffixOnlyForRealCollisions()
    {
        $this->seed(AutomotiveSettingSeeder::class);
        Settings::update('automotive-vehicles-url_building_format', 'vrm-make-model');
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'auto-update-vehicle-slug',
            'value' => 'boolean',
            'type' => true,
        ]);

        $make = VehicleMake::factory()->create(['name' => 'BMW']);
        $model = VehicleModel::factory()->create(['name' => 'i3']);

        // Two vehicles with different regs (distinct slugs under vrm-make-model)
        // whose titles will collide once the strategy switches to title.
        $first = Vehicle::factory()->create([
            'make_id' => $make->id,
            'model_id' => $model->id,
            'registration_number' => 'foo',
            'title' => 'shared title',
            'slug' => null,
        ]);
        $second = Vehicle::factory()->create([
            'make_id' => $make->id,
            'model_id' => $model->id,
            'registration_number' => 'bar',
            'title' => 'shared title',
            'slug' => null,
        ]);

        $this->assertEquals('foo-bmw-i3', $first->slug);
        $this->assertEquals('bar-bmw-i3', $second->slug);

        Settings::update('automotive-vehicles-url_building_format', 'title');

        // Touch a tracked field on each so the saving observer rebuilds the slug.
        $first->update(['title' => 'shared title final']);
        $second->update(['title' => 'shared title final']);

        $this->assertEquals('shared-title-final', $first->fresh()->slug);
        $this->assertNotEquals('shared-title-final', $second->fresh()->slug);
        $this->assertStringStartsWith('shared-title-final-', $second->fresh()->slug);
        $this->assertCount(1, Vehicle::query()->where('slug', 'shared-title-final')->get());
    }

    public function testVehicleReportFunctions()
    {
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'distance measurement',
            'config_key' => 'automotive-distance_measurement',
            'value' => 'mi',
            'type' => 'string',
        ]);

        $vehicle = Vehicle::factory()->create([
            'created_at' => '2020-11-23 01:23:45',
            'first_registration_date' => '2019-01-02',
            'engine_size_cc' => 1234,
        ]);

        // valid enquiries for this vehicle
        Enquiry::factory(3)->create([
            'reason_type' => 'vehicle',
            'reason_id' => $vehicle->id,
            'created_at' => Carbon::now()->subHours(10),
        ]);

        // valid enquiries for different vehicle
        Enquiry::factory(4)->create([
            'reason_type' => 'vehicle',
            'reason_id' => $vehicle->id + 1,
            'created_at' => Carbon::now()->subHours(10),
        ]);

        // invalid enquiries for this vehicle
        Enquiry::factory(4)->create([
            'reason_type' => 'vehicle',
            'reason_id' => $vehicle->id,
            'created_at' => Carbon::now()->subDays(2),
        ]);

        ResourceView::query()->create([
            'viewable_type' => 'vehicle',
            'viewable_id' => $vehicle->id,
            'hits' => 5,
            'date' => '2020-01-01',
        ]);

        ResourceView::query()->create([
            'viewable_type' => 'vehicle',
            'viewable_id' => $vehicle->id,
            'hits' => 3,
            'date' => '2020-02-02',
        ]);

        ResourceView::query()->create([
            'viewable_type' => 'vehicle',
            'viewable_id' => $vehicle->id + 1,
            'hits' => 7,
            'date' => '2020-02-02',
        ]);

        $this->assertEquals('2020-11-23', $vehicle->dateCreated);
        $this->assertEquals(1.3, $vehicle->engineSizeLitres);
        $this->assertEquals('mi', $vehicle->odometerUnits);
        $this->assertEquals(3, $vehicle->videoCount);
        $this->assertEquals(2019, $vehicle->registrationYear);
        $this->assertEquals(3, $vehicle->last24HoursEnquiries()->count());
        $this->assertTrue($vehicle->hasVideo);
        $this->assertEquals(8, $vehicle->impressions);
    }

    public function testRepositorySlugType()
    {
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'automotive-vehicles-url_building_format',
            'type' => 'string',
            'value' => 'title',
        ]);

        $vehicle = Vehicle::factory()->make([
            'title' => 'foo',
            'uuid' => 'bar/baz',
        ]);

        // assert that we get the expected slug based on config setting
        $this->assertEquals('foo', (App::make(VehicleRepository::class)->buildSlug($vehicle)));

        // assert that we get expected sanitised slug based on requested slug type
        $this->assertEquals('barbaz', (App::make(VehicleRepository::class)->buildSlug($vehicle, 'uuid')));
    }
}
