<?php

namespace Tests\Tenant;

use App\Events\NewVehicleImported;
use App\Facades\Feature;
use App\Facades\Settings;
use App\Master\Models\CapVehicle;
use App\VehicleSpec\Jobs\VehicleCapIdLookup;
use App\VehicleSpec\Jobs\VehicleCapIdLookupViaDatabase;
use App\VehicleSpec\Services\CapVehicleLookupService;
use App\VehicleType;
use Database\Seeders\Tenant\CapIdLocalLookupSettingSeeder;
use Database\Seeders\Tenant\VehicleSpecSeeder;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Schema;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\Notifications\Facades\Notification;
use Tests\TenantTestCase;

class CapVehicleDatabaseLookupTest extends TenantTestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        $this->pointCentralConnectionToTestDb();
        $this->createCapVehiclesTable();
    }

    private function pointCentralConnectionToTestDb(): void
    {
        Config::set('tenancy.database.central_connection', config('database.default'));
    }

    private function createCapVehiclesTable(): void
    {
        if (Schema::hasTable('cap_vehicles')) {
            return;
        }

        Schema::create('cap_vehicles', function (Blueprint $table) {
            $table->id();
            $table->string('cap_code', 20)->index();
            $table->unsignedInteger('cap_id')->index();
            $table->string('manufacturer', 100)->index();
            $table->string('short_model', 100)->index();
            $table->string('model', 200);
            $table->smallInteger('model_introduced')->nullable();
            $table->smallInteger('model_discontinued')->nullable();
            $table->string('short_derivative', 200)->nullable();
            $table->string('derivative', 500)->nullable();
            $table->smallInteger('derivative_introduced_year')->nullable();
            $table->smallInteger('derivative_discontinued_year')->nullable();
            $table->boolean('available_new_vehicle')->default(false);
            $table->string('type', 10)->index();
            $table->unsignedBigInteger('make_id')->nullable();
            $table->unsignedBigInteger('model_id')->nullable();
            $table->timestamps();
        });
    }

    private function createCapVehicle(array $overrides = []): CapVehicle
    {
        return CapVehicle::query()->create(array_merge([
            'cap_code' => 'BMWX501AA1',
            'cap_id' => 12345,
            'manufacturer' => 'BMW',
            'short_model' => 'X5',
            'model' => 'X5 DIESEL ESTATE',
            'short_derivative' => 'xDrive30d M Sport',
            'derivative' => 'xDrive30d M Sport 5dr Auto',
            'model_introduced' => 2018,
            'model_discontinued' => null,
            'derivative_introduced_year' => 2019,
            'derivative_discontinued_year' => null,
            'available_new_vehicle' => true,
            'type' => VehicleType::CAR->value,
        ], $overrides));
    }

    private function makeVehicleForLookup(array $attributes = []): Vehicle
    {
        $vehicle = new Vehicle();
        $vehicle->master_make_name = $attributes['master_make_name'] ?? 'BMW';
        $vehicle->master_model_name = $attributes['master_model_name'] ?? 'X5';
        $vehicle->type = $attributes['type'] ?? VehicleType::CAR->value;
        $vehicle->first_registration_date = $attributes['registration_date'] ?? null;
        $vehicle->derivative = $attributes['derivative'] ?? null;

        return $vehicle;
    }

    // ── Lookup Service Tests ──────────────────────────────────────────

    public function testLookupFindsExactMatch()
    {
        $this->createCapVehicle();
        $vehicle = $this->makeVehicleForLookup();

        $result = app(CapVehicleLookupService::class)->lookup($vehicle);

        $this->assertNotNull($result);
        $this->assertEquals(12345, $result['cap_id']);
        $this->assertEquals('BMWX501AA1', $result['cap_code']);
        $this->assertEquals('Car', $result['type']);
    }

    public function testLookupReturnsNullWhenNoMatch()
    {
        $this->createCapVehicle();
        $vehicle = $this->makeVehicleForLookup(['master_make_name' => 'Tesla', 'master_model_name' => 'Model 3']);

        $result = app(CapVehicleLookupService::class)->lookup($vehicle);

        $this->assertNull($result);
    }

    public function testLookupReturnsNullWhenMakeEmpty()
    {
        $this->createCapVehicle();
        $vehicle = $this->makeVehicleForLookup(['master_make_name' => '', 'master_model_name' => 'X5']);

        $result = app(CapVehicleLookupService::class)->lookup($vehicle);

        $this->assertNull($result);
    }

    public function testLookupReturnsNullWhenModelEmpty()
    {
        $this->createCapVehicle();
        $vehicle = $this->makeVehicleForLookup(['master_make_name' => 'BMW', 'master_model_name' => '']);

        $result = app(CapVehicleLookupService::class)->lookup($vehicle);

        $this->assertNull($result);
    }

    public function testLookupPrefersExactShortModelMatch()
    {
        $this->createCapVehicle(['cap_id' => 11111, 'short_model' => 'X5', 'model' => 'X5 DIESEL ESTATE']);
        $this->createCapVehicle(['cap_code' => 'BMWX502AA1', 'cap_id' => 22222, 'short_model' => 'X5 M', 'model' => 'X5 M COMPETITION']);

        $vehicle = $this->makeVehicleForLookup(['master_model_name' => 'X5']);

        $result = app(CapVehicleLookupService::class)->lookup($vehicle);

        $this->assertNotNull($result);
        $this->assertEquals(11111, $result['cap_id']);
    }

    public function testLookupMatchesDerivative()
    {
        $this->createCapVehicle([
            'cap_id' => 11111,
            'short_derivative' => 'xDrive30d M Sport',
            'derivative' => 'xDrive30d M Sport 5dr Auto',
        ]);
        $this->createCapVehicle([
            'cap_code' => 'BMWX502AA1',
            'cap_id' => 22222,
            'short_derivative' => 'xDrive40i',
            'derivative' => 'xDrive40i 5dr Auto',
        ]);

        $vehicle = $this->makeVehicleForLookup(['derivative' => 'xDrive30d M Sport']);

        $result = app(CapVehicleLookupService::class)->lookup($vehicle);

        $this->assertNotNull($result);
        $this->assertEquals(11111, $result['cap_id']);
    }

    public function testLookupFiltersActiveByYear()
    {
        $this->createCapVehicle([
            'cap_id' => 11111,
            'model_introduced' => 2010,
            'model_discontinued' => 2017,
        ]);
        $this->createCapVehicle([
            'cap_code' => 'BMWX502AA1',
            'cap_id' => 22222,
            'model_introduced' => 2018,
            'model_discontinued' => null,
        ]);

        $vehicle = $this->makeVehicleForLookup(['registration_date' => '2020-06-15']);

        $result = app(CapVehicleLookupService::class)->lookup($vehicle);

        $this->assertNotNull($result);
        $this->assertEquals(22222, $result['cap_id']);
    }

    public function testLookupMatchesCaseInsensitive()
    {
        $this->createCapVehicle(['manufacturer' => 'MERCEDES-BENZ', 'short_model' => 'C CLASS']);

        $vehicle = $this->makeVehicleForLookup([
            'master_make_name' => 'mercedes-benz',
            'master_model_name' => 'c class',
        ]);

        $result = app(CapVehicleLookupService::class)->lookup($vehicle);

        $this->assertNotNull($result);
        $this->assertEquals(12345, $result['cap_id']);
    }

    public function testLookupDetectsLcvType()
    {
        $this->createCapVehicle([
            'type' => VehicleType::LCV->value,
            'manufacturer' => 'FORD',
            'short_model' => 'TRANSIT',
            'model' => 'TRANSIT CUSTOM',
        ]);

        $vehicle = $this->makeVehicleForLookup([
            'master_make_name' => 'Ford',
            'master_model_name' => 'Transit',
            'type' => 'lcv',
        ]);

        $result = app(CapVehicleLookupService::class)->lookup($vehicle);

        $this->assertNotNull($result);
        $this->assertEquals('LCV', $result['type']);
    }

    public function testLookupDetectsVanAsLcv()
    {
        $this->createCapVehicle([
            'type' => VehicleType::LCV->value,
            'manufacturer' => 'FORD',
            'short_model' => 'TRANSIT',
            'model' => 'TRANSIT CUSTOM',
        ]);

        $vehicle = $this->makeVehicleForLookup([
            'master_make_name' => 'Ford',
            'master_model_name' => 'Transit',
            'type' => 'van',
        ]);

        $result = app(CapVehicleLookupService::class)->lookup($vehicle);

        $this->assertNotNull($result);
    }

    // ── Job Tests ─────────────────────────────────────────────────────

    public function testJobUpdatesVehicleCapId()
    {
        $this->createCapVehicle(['cap_id' => 99999, 'cap_code' => 'TESTCODE1']);

        $vehicle = Vehicle::factory()->create([
            'cap_id' => null,
            'cap_code' => null,
            'type' => VehicleType::CAR->value,
        ]);
        $vehicle->master_make_name = 'BMW';
        $vehicle->master_model_name = 'X5';

        Notification::shouldReceive('addNotification')->never();

        $job = new VehicleCapIdLookupViaDatabase($vehicle);
        $job->handle();

        $vehicle->refresh();
        $this->assertEquals(99999, $vehicle->cap_id);
        $this->assertEquals('TESTCODE1', $vehicle->cap_code);
    }

    public function testJobSkipsVehicleWithExistingCapId()
    {
        $vehicle = Vehicle::factory()->create([
            'cap_id' => 55555,
            'type' => VehicleType::CAR->value,
        ]);

        $job = new VehicleCapIdLookupViaDatabase($vehicle);
        $job->handle();

        $vehicle->refresh();
        $this->assertEquals(55555, $vehicle->cap_id);
    }

    public function testJobCreatesNotificationOnNoMatch()
    {
        $vehicle = Vehicle::factory()->create([
            'cap_id' => null,
            'type' => VehicleType::CAR->value,
        ]);
        $vehicle->master_make_name = 'NoMatch';
        $vehicle->master_model_name = 'NoModel';

        Notification::shouldReceive('addNotification')
            ->once()
            ->withArgs(function ($type, $message) {
                return $type === 'warning'
                    && str_contains($message, 'Could not find CAP ID');
            });

        $job = new VehicleCapIdLookupViaDatabase($vehicle);
        $job->handle();
    }

    // ── Listener / Feature Flag Tests ─────────────────────────────────

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

        Queue::fake();
        Feature::setEnabled(tenant(), ['cap-database']);
        Settings::update('cap-id-lookup-local-enabled', true);

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

        Event::dispatch(new NewVehicleImported($vehicle, []));

        Queue::assertPushed(VehicleCapIdLookupViaDatabase::class);
        Queue::assertNotPushed(VehicleCapIdLookup::class);
    }

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

        Queue::fake();
        Settings::update('cap-id-lookup-local-enabled', true);
        Settings::update('vehicle-spec-providers-cap-enabled', true);

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

        Event::dispatch(new NewVehicleImported($vehicle, []));

        Queue::assertNotPushed(VehicleCapIdLookupViaDatabase::class);
        Queue::assertPushed(VehicleCapIdLookup::class);
    }

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

        Queue::fake();
        Feature::setEnabled(tenant(), ['cap-database']);
        Settings::update('cap-id-lookup-local-enabled', false);
        Settings::update('vehicle-spec-providers-cap-enabled', true);

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

        Event::dispatch(new NewVehicleImported($vehicle, []));

        Queue::assertNotPushed(VehicleCapIdLookupViaDatabase::class);
        Queue::assertPushed(VehicleCapIdLookup::class);
    }

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

        Queue::fake();
        Feature::setEnabled(tenant(), ['cap-database']);
        Settings::update('cap-id-lookup-local-enabled', true);

        $vehicle = Vehicle::factory()->create(['cap_id' => 'existing-id']);

        Event::dispatch(new NewVehicleImported($vehicle, []));

        Queue::assertNotPushed(VehicleCapIdLookupViaDatabase::class);
        Queue::assertNotPushed(VehicleCapIdLookup::class);
    }

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

        Queue::fake();
        Feature::setEnabled(tenant(), ['cap-database']);
        Settings::update('cap-id-lookup-local-enabled', true);
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'Specification / Feature Synchronization',
            'group' => 'HPI',
            'name' => 'HPI CAP ID Lookup Enabled',
            'config_key' => 'cap-id-lookup-hpi-enabled',
            'type' => 'boolean',
            'value' => true,
        ]);

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

        Event::dispatch(new NewVehicleImported($vehicle, []));

        Queue::assertPushed(VehicleCapIdLookupViaDatabase::class);
        Queue::assertNotPushed(\App\VehicleSpec\Jobs\VehicleCapIdLookupViaHPI::class);
    }
}
