<?php

namespace Tests\Tenant;

use App\Facades\Settings;
use App\Jobs\ImportMotorsVehicleBatch;
use App\Modules\Stock\Motors;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;
use Mockery;
use Mtc\ContentManager\Contracts\Media;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\TenantType;
use Mtc\MercuryDataModels\Vehicle;
use Tests\TenantTestCase;

class MotorsTest extends TenantTestCase
{
    private function makeSetting(string $key, mixed $value, string $type = 'boolean'): void
    {
        Settings::make([
            'tab' => 'foo',
            'section' => 'foo',
            'group' => 'foo',
            'name' => 'foo',
            'config_key' => $key,
            'value' => $value,
            'type' => $type,
        ]);
    }

    private function setupDefaults(): void
    {
        $this->makeSetting('stock-motors-enabled', true);
        $this->makeSetting('stock-motors-ftp-username', 'testuser', 'string');
        $this->makeSetting('stock-motors-ftp-password', 'testpass', 'string');
        $this->makeSetting('stock-motors-sync-with-price', false);
        $this->makeSetting('stock-motors-sync-min-price', '', 'string');
        $this->makeSetting('stock-motors-sync-max-age', '', 'string');
        $this->makeSetting('stock-motors-sync-max-mileage', '', 'string');
    }

    public function testEnabled()
    {
        $this->makeSetting('stock-motors-enabled', false);
        $this->assertFalse((new Motors())->enabled());

        Settings::update('stock-motors-enabled', true);
        $this->assertTrue((new Motors())->enabled());
    }

    public function testName()
    {
        $this->assertEquals('Motors', (new Motors())->name());
    }

    public function testDealershipAdditionalDataFields()
    {
        $fields = (new Motors())->dealershipAdditionalDataFields();
        $this->assertArrayHasKey('motors-location-id', $fields);
        $this->assertEquals('text', $fields['motors-location-id']['type']);
    }

    public function testExternalMediaUrl()
    {
        $motors = new Motors();

        $media = Mockery::mock(Media::class)->makePartial();
        $media->src = '/path/to/image.jpg';

        $url = $motors->url($media, 'large');
        $this->assertEquals('https://origin-resizer.images.autoexposure.co.uk/path/to/image.jpg', $url);
    }

    public function testExternalMediaUrlTrimsLeadingSlash()
    {
        $motors = new Motors();

        $media = Mockery::mock(Media::class)->makePartial();
        $media->src = '//double/slash/image.jpg';

        $url = $motors->url($media, 'medium');
        $this->assertStringNotContainsString('///', $url);
    }

    public function testFilterByPriceAllowsAllWhenDisabled()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-with-price', false);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByPrice');
        $method->setAccessible(true);

        $vehicle = collect(['price' => 0]);
        $this->assertTrue($method->invoke($motors, $vehicle));
    }

    public function testFilterByPriceRejectsZeroPriceWhenEnabled()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-with-price', true);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByPrice');
        $method->setAccessible(true);

        $vehicle = collect(['price' => 0]);
        $this->assertFalse($method->invoke($motors, $vehicle));
    }

    public function testFilterByPriceAcceptsPositivePriceWhenEnabled()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-with-price', true);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByPrice');
        $method->setAccessible(true);

        $vehicle = collect(['price' => 15000]);
        $this->assertTrue($method->invoke($motors, $vehicle));
    }

    public function testFilterByPriceRejectsBelowMinPrice()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-min-price', 5000);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByPrice');
        $method->setAccessible(true);

        $vehicle = collect(['price' => 3000]);
        $this->assertFalse($method->invoke($motors, $vehicle));
    }

    public function testFilterByPriceAcceptsAboveMinPrice()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-min-price', 5000);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByPrice');
        $method->setAccessible(true);

        $vehicle = collect(['price' => 10000]);
        $this->assertTrue($method->invoke($motors, $vehicle));
    }

    public function testFilterByAgeAllowsAllWhenNoMaxAge()
    {
        $this->setupDefaults();

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByAge');
        $method->setAccessible(true);

        $vehicle = collect(['regyear' => '2000']);
        $this->assertTrue($method->invoke($motors, $vehicle));
    }

    public function testFilterByAgeRejectsOldVehicle()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-max-age', 5);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByAge');
        $method->setAccessible(true);

        $oldYear = (int) date('Y') - 10;
        $vehicle = collect(['regyear' => (string) $oldYear]);
        $this->assertFalse($method->invoke($motors, $vehicle));
    }

    public function testFilterByAgeAcceptsRecentVehicle()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-max-age', 5);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByAge');
        $method->setAccessible(true);

        $recentYear = (int) date('Y') - 2;
        $vehicle = collect(['regyear' => (string) $recentYear]);
        $this->assertTrue($method->invoke($motors, $vehicle));
    }

    public function testFilterByAgeRejectsEmptyYear()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-max-age', 5);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByAge');
        $method->setAccessible(true);

        $vehicle = collect(['regyear' => '']);
        $this->assertFalse($method->invoke($motors, $vehicle));
    }

    public function testFilterByAgeRejectsFutureYear()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-max-age', 5);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByAge');
        $method->setAccessible(true);

        $futureYear = (int) date('Y') + 1;
        $vehicle = collect(['regyear' => (string) $futureYear]);
        $this->assertFalse($method->invoke($motors, $vehicle));
    }

    public function testFilterByMileageAllowsAllWhenNoMaxMileage()
    {
        $this->setupDefaults();

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByMileage');
        $method->setAccessible(true);

        $vehicle = collect(['mileage' => 999999]);
        $this->assertTrue($method->invoke($motors, $vehicle));
    }

    public function testFilterByMileageRejectsHighMileage()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-max-mileage', 50000);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByMileage');
        $method->setAccessible(true);

        $vehicle = collect(['mileage' => 60000]);
        $this->assertFalse($method->invoke($motors, $vehicle));
    }

    public function testFilterByMileageAcceptsLowMileage()
    {
        $this->setupDefaults();
        Settings::update('stock-motors-sync-max-mileage', 50000);

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterByMileage');
        $method->setAccessible(true);

        $vehicle = collect(['mileage' => 30000]);
        $this->assertTrue($method->invoke($motors, $vehicle));
    }

    public function testFilterVehicleRejectsBikes()
    {
        $this->setupDefaults();

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterVehicle');
        $method->setAccessible(true);

        $vehicle = collect(['price' => 5000, 'regyear' => date('Y'), 'mileage' => 1000, 'vehicletype' => 'BIKE']);
        $this->assertFalse($method->invoke($motors, $vehicle));
    }

    public function testFilterVehicleAcceptsCars()
    {
        $this->setupDefaults();

        $motors = new Motors();
        $method = new \ReflectionMethod(Motors::class, 'filterVehicle');
        $method->setAccessible(true);

        $vehicle = collect(['price' => 5000, 'regyear' => date('Y'), 'mileage' => 1000, 'vehicletype' => 'CAR']);
        $this->assertTrue($method->invoke($motors, $vehicle));
    }

    public function testCollectionDispatchesBatchJob()
    {
        Queue::fake();
        $this->setupDefaults();

        Dealership::factory()->create(['data' => ['motors-location-id' => 'LOC-1']]);

        $motors = new Motors();
        $batch = collect([
            collect([
                'vehicleid' => 'V001',
                'price' => 15000,
                'regyear' => date('Y'),
                'mileage' => 10000,
                'vehicletype' => 'CAR',
            ]),
        ]);

        $motors->collection($batch, 'test.csv');

        Queue::assertPushed(ImportMotorsVehicleBatch::class);
    }

    public function testCollectionDoesNotDispatchWhenAllFiltered()
    {
        Queue::fake();
        $this->setupDefaults();
        Settings::update('stock-motors-sync-with-price', true);

        $motors = new Motors();
        $batch = collect([
            collect([
                'vehicleid' => 'V001',
                'price' => 0,
                'regyear' => date('Y'),
                'mileage' => 10000,
                'vehicletype' => 'CAR',
            ]),
        ]);

        $motors->collection($batch, 'test.csv');

        Queue::assertNotPushed(ImportMotorsVehicleBatch::class);
    }

    public function testSftpFilesAreCopiedToLocal()
    {
        Queue::fake();
        $this->setupDefaults();

        Storage::fake('motors-sftp');
        Storage::fake('local');

        Storage::disk('motors-sftp')->put('stock.csv', "VehicleId,Price\nV001,15000\n");
        Dealership::factory()->create(['data' => ['motors-location-id' => 'LOC-1']]);

        (new Motors())->runScheduledImport();

        // Verify file was copied from SFTP to local and then cleaned up
        Storage::disk('local')->assertMissing('stock.csv');
    }

    public function testAutoCreateDealershipWhenListingTenantAndSettingEnabled()
    {
        $this->setupDefaults();
        tenant()->type = TenantType::LISTING->value;
        $this->makeSetting('stock-motors-auto-create-dealerships', true);

        $batch = collect([
            collect([
                'vehicleid' => 'V-AUTO-DEALER-1',
                'dealerid' => 'MOTORS-LOC-100',
                'location-name' => 'Test Dealer Name',
                'make' => 'Ford',
                'model' => 'Focus',
                'price' => 15000,
                'mileage' => 10000,
                'regyear' => date('Y'),
                'registration-date' => '',
                'variant' => 'Zetec',
                'colour' => 'Blue',
                'fueltype' => 'Petrol',
                'transmission' => 'Manual',
                'body-type' => 'Hatchback',
                'vehicletype' => 'CAR',
                'vrm' => 'AB12CDE',
                'capid' => '',
                'doors' => '5',
                'seats' => '5',
                'co2' => '120',
                'engine-size' => '1600',
                'mpg' => '50',
                'imageurls' => '',
                'postcode' => 'NG1 1AA',
                'insurance-group' => '10',
                'description' => 'Test vehicle',
            ]),
        ]);

        $job = new ImportMotorsVehicleBatch($batch, collect(), 'test.csv');
        $job->handle();

        $this->assertDatabaseHas('dealerships', [
            'name' => 'Test Dealer Name',
            'postcode' => 'NG1 1AA',
            'active' => true,
        ]);

        $dealership = Dealership::query()->where('name', 'Test Dealer Name')->first();
        $this->assertEquals('MOTORS-LOC-100', $dealership->data['motors-location-id']);

        $vehicle = Vehicle::query()->where('uuid', 'V-AUTO-DEALER-1')->first();
        $this->assertEquals($dealership->id, $vehicle->dealership_id);
    }

    public function testDoesNotAutoCreateDealershipWhenSettingDisabled()
    {
        $this->setupDefaults();
        $this->makeSetting('stock-motors-auto-create-dealerships', false);

        $batch = collect([
            collect([
                'vehicleid' => 'V-NO-DEALER-1',
                'dealerid' => 'MOTORS-LOC-200',
                'location-name' => 'Should Not Exist',
                'make' => 'Ford',
                'model' => 'Focus',
                'price' => 15000,
                'mileage' => 10000,
                'regyear' => date('Y'),
                'registration-date' => '',
                'variant' => 'Zetec',
                'colour' => 'Blue',
                'fueltype' => 'Petrol',
                'transmission' => 'Manual',
                'body-type' => 'Hatchback',
                'vehicletype' => 'CAR',
                'vrm' => 'CD34EFG',
                'capid' => '',
                'doors' => '5',
                'seats' => '5',
                'co2' => '120',
                'engine-size' => '1600',
                'mpg' => '50',
                'imageurls' => '',
                'postcode' => 'NG1 1AA',
                'insurance-group' => '10',
                'description' => 'Test vehicle',
            ]),
        ]);

        $job = new ImportMotorsVehicleBatch($batch, collect(), 'test.csv');
        $job->handle();

        $this->assertDatabaseMissing('dealerships', [
            'name' => 'Should Not Exist',
        ]);

        $vehicle = Vehicle::query()->where('uuid', 'V-NO-DEALER-1')->first();
        $this->assertNull($vehicle->dealership_id);
    }

    public function testUsesExistingDealershipInsteadOfCreatingDuplicate()
    {
        $this->setupDefaults();
        tenant()->type = TenantType::LISTING->value;
        $this->makeSetting('stock-motors-auto-create-dealerships', true);

        $existingDealership = Dealership::factory()->create([
            'name' => 'Existing Dealer',
            'data' => ['motors-location-id' => 'MOTORS-LOC-300'],
        ]);

        $batch = collect([
            collect([
                'vehicleid' => 'V-EXISTING-DEALER-1',
                'dealerid' => 'MOTORS-LOC-300',
                'location-name' => 'Existing Dealer',
                'make' => 'Ford',
                'model' => 'Focus',
                'price' => 15000,
                'mileage' => 10000,
                'regyear' => date('Y'),
                'registration-date' => '',
                'variant' => 'Zetec',
                'colour' => 'Blue',
                'fueltype' => 'Petrol',
                'transmission' => 'Manual',
                'body-type' => 'Hatchback',
                'vehicletype' => 'CAR',
                'vrm' => 'EF56GHI',
                'capid' => '',
                'doors' => '5',
                'seats' => '5',
                'co2' => '120',
                'engine-size' => '1600',
                'mpg' => '50',
                'imageurls' => '',
                'postcode' => 'NG1 1AA',
                'insurance-group' => '10',
                'description' => 'Test vehicle',
            ]),
        ]);

        $dealerships = collect([(object) [
            'id' => $existingDealership->id,
            'stock_location' => 'MOTORS-LOC-300',
        ]]);

        $job = new ImportMotorsVehicleBatch($batch, $dealerships, 'test.csv');
        $job->handle();

        $this->assertEquals(1, Dealership::query()->where('name', 'Existing Dealer')->count());

        $vehicle = Vehicle::query()->where('uuid', 'V-EXISTING-DEALER-1')->first();
        $this->assertEquals($existingDealership->id, $vehicle->dealership_id);
    }

    public function testMultipleVehiclesSameDealerCreatesOnlyOneDealership()
    {
        $this->setupDefaults();
        tenant()->type = TenantType::LISTING->value;
        $this->makeSetting('stock-motors-auto-create-dealerships', true);

        $vehicleData = [
            'dealerid' => 'MOTORS-LOC-400',
            'location-name' => 'Shared Dealer',
            'make' => 'Ford',
            'model' => 'Focus',
            'price' => 15000,
            'mileage' => 10000,
            'regyear' => date('Y'),
            'registration-date' => '',
            'variant' => 'Zetec',
            'colour' => 'Blue',
            'fueltype' => 'Petrol',
            'transmission' => 'Manual',
            'body-type' => 'Hatchback',
            'vehicletype' => 'CAR',
            'capid' => '',
            'doors' => '5',
            'seats' => '5',
            'co2' => '120',
            'engine-size' => '1600',
            'mpg' => '50',
            'imageurls' => '',
            'postcode' => 'NG1 1AA',
            'insurance-group' => '10',
            'description' => 'Test vehicle',
        ];

        $batch = collect([
            collect(array_merge($vehicleData, ['vehicleid' => 'V-MULTI-1', 'vrm' => 'AA11BBB'])),
            collect(array_merge($vehicleData, ['vehicleid' => 'V-MULTI-2', 'vrm' => 'CC22DDD'])),
            collect(array_merge($vehicleData, ['vehicleid' => 'V-MULTI-3', 'vrm' => 'EE33FFF'])),
        ]);

        $job = new ImportMotorsVehicleBatch($batch, collect(), 'test.csv');
        $job->handle();

        $this->assertEquals(1, Dealership::query()->where('name', 'Shared Dealer')->count());

        $dealership = Dealership::query()->where('name', 'Shared Dealer')->first();
        $this->assertEquals(3, Vehicle::query()->where('dealership_id', $dealership->id)->count());
    }

    public function testExistingVehicleGetsDealershipIdBackfilled()
    {
        $this->setupDefaults();
        tenant()->type = TenantType::LISTING->value;
        $this->makeSetting('stock-motors-auto-create-dealerships', true);

        $existingVehicle = Vehicle::factory()->create([
            'stock_provider' => 'motors',
            'uuid' => 'V-BACKFILL-1',
            'dealership_id' => null,
        ]);

        $batch = collect([
            collect([
                'vehicleid' => 'V-BACKFILL-1',
                'dealerid' => 'MOTORS-LOC-500',
                'location-name' => 'Backfill Dealer',
                'make' => 'Ford',
                'model' => 'Focus',
                'price' => 15000,
                'mileage' => 10000,
                'regyear' => date('Y'),
                'registration-date' => '',
                'variant' => 'Zetec',
                'colour' => 'Blue',
                'fueltype' => 'Petrol',
                'transmission' => 'Manual',
                'body-type' => 'Hatchback',
                'vehicletype' => 'CAR',
                'vrm' => 'GH78IJK',
                'capid' => '',
                'doors' => '5',
                'seats' => '5',
                'co2' => '120',
                'engine-size' => '1600',
                'mpg' => '50',
                'imageurls' => '',
                'postcode' => 'NG2 2BB',
                'insurance-group' => '10',
                'description' => 'Test vehicle',
            ]),
        ]);

        $job = new ImportMotorsVehicleBatch($batch, collect(), 'test.csv');
        $job->handle();

        $existingVehicle->refresh();
        $this->assertNotNull($existingVehicle->dealership_id);

        $dealership = Dealership::query()->where('name', 'Backfill Dealer')->first();
        $this->assertEquals($dealership->id, $existingVehicle->dealership_id);
    }

    public function testTwoSequentialBatchesShareDealerWithoutDuplicating()
    {
        $this->setupDefaults();
        tenant()->type = TenantType::LISTING->value;
        $this->makeSetting('stock-motors-auto-create-dealerships', true);

        $vehicleData = [
            'dealerid' => 'MOTORS-LOC-SHARED',
            'location-name' => 'Shared Across Batches',
            'make' => 'Ford',
            'model' => 'Focus',
            'price' => 15000,
            'mileage' => 10000,
            'regyear' => date('Y'),
            'registration-date' => '',
            'variant' => 'Zetec',
            'colour' => 'Blue',
            'fueltype' => 'Petrol',
            'transmission' => 'Manual',
            'body-type' => 'Hatchback',
            'vehicletype' => 'CAR',
            'capid' => '',
            'doors' => '5',
            'seats' => '5',
            'co2' => '120',
            'engine-size' => '1600',
            'mpg' => '50',
            'imageurls' => '',
            'postcode' => 'NG3 3CC',
            'insurance-group' => '10',
            'description' => 'Test vehicle',
        ];

        $batchOne = collect([
            collect(array_merge($vehicleData, ['vehicleid' => 'V-SEQ-1', 'vrm' => 'AA11AAA'])),
            collect(array_merge($vehicleData, ['vehicleid' => 'V-SEQ-2', 'vrm' => 'BB22BBB'])),
        ]);

        $batchTwo = collect([
            collect(array_merge($vehicleData, ['vehicleid' => 'V-SEQ-3', 'vrm' => 'CC33CCC'])),
            collect(array_merge($vehicleData, ['vehicleid' => 'V-SEQ-4', 'vrm' => 'DD44DDD'])),
        ]);

        // Each job receives its own snapshot of the dealerships collection,
        // simulating how parallel workers receive stale snapshots at dispatch time.
        $jobOne = new ImportMotorsVehicleBatch($batchOne, collect(), 'test.csv');
        $jobOne->handle();

        $jobTwo = new ImportMotorsVehicleBatch($batchTwo, collect(), 'test.csv');
        $jobTwo->handle();

        $this->assertEquals(
            1,
            Dealership::query()->where('name', 'Shared Across Batches')->count(),
            'Sequential batches sharing a dealer should not produce duplicates'
        );

        $dealership = Dealership::query()->where('name', 'Shared Across Batches')->first();
        $this->assertEquals(4, Vehicle::query()->where('dealership_id', $dealership->id)->count());
    }

    public function testRemovesOldVehiclesAfterSync()
    {
        Queue::fake();
        $this->setupDefaults();

        // Create a vehicle that should be removed
        Vehicle::factory()->create([
            'stock_provider' => 'motors',
            'uuid' => 'OLD-V001',
            'was_recently_synced' => false,
            'pending_stock_sync' => null,
        ]);

        Storage::fake('motors-sftp');
        Storage::fake('local');

        Storage::disk('motors-sftp')->put('stock.csv', "VehicleId,Price\nV001,15000\n");
        Dealership::factory()->create(['data' => ['motors-location-id' => 'LOC-1']]);

        (new Motors())->runScheduledImport();

        $this->assertFalse(Vehicle::query()->where('uuid', 'OLD-V001')->exists());
    }
}
