<?php

namespace Tests\Tenant;

use App\Facades\Settings;
use App\Imports\VehicleImport;
use App\Jobs\ImportImagesFromUrlList;
use App\Models\ImportMap;
use App\Models\ImportMapField;
use Mtc\MercuryDataModels\VehicleType;
use Illuminate\Support\Facades\Queue;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleAttribute;
use Mtc\MercuryDataModels\VehicleAttributeValue;
use Tests\TenantTestCase;

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

    /**
     * Create a VehicleImport instance without calling the constructor
     * (which queries the DB for taxonomy data via loadRelationships).
     */
    private function makeImport(): VehicleImport
    {
        $ref = new \ReflectionClass(VehicleImport::class);
        /** @var VehicleImport $import */
        $import = $ref->newInstanceWithoutConstructor();

        // Set required private properties
        $importTypeProp = $ref->getProperty('importType');
        $importTypeProp->setAccessible(true);
        $importTypeProp->setValue($import, 'default');

        $importMapProp = $ref->getProperty('import_map');
        $importMapProp->setAccessible(true);
        $importMapProp->setValue($import, null);

        // Set relationship collections to empty so methods don't NPE
        foreach (['dealerships', 'makes', 'models', 'fuel_types', 'body_styles', 'drivetrains', 'transmissions'] as $prop) {
            $p = $ref->getProperty($prop);
            $p->setAccessible(true);
            $p->setValue($import, collect());
        }

        // Set vehicleAttributes (from VehicleImportFlow trait)
        $attrProp = new \ReflectionProperty($import, 'vehicleAttributes');
        $attrProp->setAccessible(true);
        $attrProp->setValue($import, []);

        // Set subFieldDelimiter
        $delimProp = new \ReflectionProperty($import, 'subFieldDelimiter');
        $delimProp->setAccessible(true);
        $delimProp->setValue($import, '|');

        // Set static vehicleFillable if empty
        $fillableProp = $ref->getProperty('vehicleFillable');
        $fillableProp->setAccessible(true);
        if (empty($fillableProp->getValue())) {
            $fillableProp->setValue(null, (new Vehicle())->getFillable());
        }

        return $import;
    }

    public function testCastToBoolean()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'castToBoolean');
        $method->setAccessible(true);

        $this->assertTrue($method->invoke($import, '1'));
        $this->assertTrue($method->invoke($import, 'yes'));
        $this->assertTrue($method->invoke($import, 'true'));
        $this->assertTrue($method->invoke($import, 'y'));
        $this->assertTrue($method->invoke($import, 'on'));
        $this->assertTrue($method->invoke($import, 'new'));
        $this->assertTrue($method->invoke($import, 'YES'));
        $this->assertTrue($method->invoke($import, 'True'));

        $this->assertFalse($method->invoke($import, '0'));
        $this->assertFalse($method->invoke($import, 'no'));
        $this->assertFalse($method->invoke($import, 'false'));
        $this->assertFalse($method->invoke($import, ''));
        $this->assertFalse($method->invoke($import, 'off'));
        $this->assertFalse($method->invoke($import, 'random'));
    }

    public function testDecodeDefaultValueConcatenatesMultipleColumnTokens()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['model_desc' => 'SPRAYER', 'model_code' => 'OKTOPUS', 'make' => 'KUHN'];

        // Two tokens joined by a literal space, interpolated in order.
        $this->assertSame('SPRAYER OKTOPUS', $method->invoke($import, '{{model_desc}} {{model_code}}', $row));
        // Three tokens.
        $this->assertSame('KUHN OKTOPUS SPRAYER', $method->invoke($import, '{{make}} {{model_code}} {{model_desc}}', $row));
        // Whitespace inside the braces is tolerated.
        $this->assertSame('SPRAYER OKTOPUS', $method->invoke($import, '{{ model_desc }} {{ model_code }}', $row));
        // A missing column resolves to empty and the result is trimmed.
        $this->assertSame('SPRAYER', $method->invoke($import, '{{model_desc}} {{missing}}', $row));
    }

    public function testDecodeDefaultValuePreservesSingleTokenAndExpressionBehaviour()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['model_desc' => 'SPRAYER', 'new_used' => 'N', 'price' => '1500'];

        // Single column reference is returned as-is.
        $this->assertSame('SPRAYER', $method->invoke($import, '{{model_desc}}', $row));
        // Single equality expression still evaluates to a boolean.
        $this->assertTrue($method->invoke($import, '{{new_used=N}}', $row));
        $this->assertFalse($method->invoke($import, '{{new_used=U}}', $row));
        // Single arithmetic expression still computes.
        $this->assertEquals(500.0, $method->invoke($import, '{{price-1000}}', $row));
        // A non-logic literal is returned unchanged.
        $this->assertSame('static', $method->invoke($import, 'static', $row));
    }

    public function testCastToVehicleType()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'castToVehicleType');
        $method->setAccessible(true);

        $this->assertEquals(VehicleType::CAR->value, $method->invoke($import, 'car', []));
        $this->assertEquals(VehicleType::LCV->value, $method->invoke($import, 'lcv', []));
        $this->assertEquals(VehicleType::MOTORCYCLE->value, $method->invoke($import, 'motorcycle', []));
        $this->assertEquals(VehicleType::CAR->value, $method->invoke($import, 'unknown', []));
        $this->assertEquals(VehicleType::CAR->value, $method->invoke($import, '', []));
    }

    public function testCastToVehicleTypeWithCustomMappings()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'castToVehicleType');
        $method->setAccessible(true);

        $mappings = [
            ['from' => 'Van', 'to' => VehicleType::LCV->value],
            ['from' => 'Truck', 'to' => VehicleType::LCV->value],
        ];

        $this->assertEquals(VehicleType::LCV->value, $method->invoke($import, 'Van', $mappings));
        $this->assertEquals(VehicleType::LCV->value, $method->invoke($import, 'van', $mappings));
        $this->assertEquals(VehicleType::LCV->value, $method->invoke($import, 'Truck', $mappings));
        $this->assertEquals(VehicleType::CAR->value, $method->invoke($import, 'Sedan', $mappings));
    }

    public function testResolveVehicleTypeWithNullDefault()
    {
        $importMap = ImportMap::factory()->create(['data' => []]);
        $importMap->load('fields');

        $import = $this->makeImport();
        $import->setImportMap($importMap);

        $method = new \ReflectionMethod($import, 'resolveVehicleType');
        $method->setAccessible(true);

        // No default — car is the fallback
        $this->assertEquals(VehicleType::CAR->value, $method->invoke($import, 'unknown', []));
        // Standard type string matching still works
        $this->assertEquals(VehicleType::LCV->value, $method->invoke($import, 'lcv', []));
        $this->assertEquals(VehicleType::MOTORCYCLE->value, $method->invoke($import, 'motorcycle', []));
    }

    public function testResolveVehicleTypeWithMotorhomeDefault()
    {
        $importMap = ImportMap::factory()->create(['data' => []]);
        ImportMapField::create([
            'map_id' => $importMap->id,
            'on_model' => 'type',
            'on_file' => '',
            'default_value' => VehicleType::MOTORHOME->value,
        ]);
        ImportMapField::create([
            'map_id' => $importMap->id,
            'on_model' => 'body_style_id',
            'on_file' => 'body_style',
        ]);
        $importMap->load('fields');

        $import = $this->makeImport();
        $import->setImportMap($importMap);

        $method = new \ReflectionMethod($import, 'resolveVehicleType');
        $method->setAccessible(true);

        // Non-campervan body type → motorhome
        $this->assertEquals(
            VehicleType::MOTORHOME->value,
            $method->invoke($import, '', ['body_style' => 'Motorhome'])
        );

        // Campervan body types → campervan
        $this->assertEquals(
            VehicleType::CAMPERVAN->value,
            $method->invoke($import, '', ['body_style' => 'Camper Van'])
        );
        $this->assertEquals(
            VehicleType::CAMPERVAN->value,
            $method->invoke($import, '', ['body_style' => 'campervan'])
        );
        $this->assertEquals(
            VehicleType::CAMPERVAN->value,
            $method->invoke($import, '', ['body_style' => 'Camper-Van'])
        );
    }

    public function testImportRowSearchParamsWithUuid()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'importRowSearchParams');
        $method->setAccessible(true);

        $row = ['uuid' => 'my-unique-id', 'registration_number' => 'AB12CDE'];
        $result = $method->invoke($import, $row);

        $this->assertEquals(['uuid' => 'my-unique-id'], $result);
    }

    public function testImportRowSearchParamsWithSlug()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'importRowSearchParams');
        $method->setAccessible(true);

        $row = ['slug' => 'ford-focus-2022', 'uuid' => 'some-uuid'];
        $result = $method->invoke($import, $row);

        $this->assertEquals(['slug' => 'ford-focus-2022'], $result);
    }

    public function testImportRowSearchParamsGeneratesUuidFromReg()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'importRowSearchParams');
        $method->setAccessible(true);

        $row = ['registration_number' => 'AB12CDE'];
        $result = $method->invoke($import, $row);

        $this->assertEquals(['uuid' => 'import-AB12CDE'], $result);
    }

    public function testImportRowSearchParamsReturnsNullWithoutIdentifier()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'importRowSearchParams');
        $method->setAccessible(true);

        $row = ['title' => 'Some Vehicle'];
        $result = $method->invoke($import, $row);

        $this->assertNull($result);
    }

    public function testImportRowSearchParamsIncludesStockProvider()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'importRowSearchParams');
        $method->setAccessible(true);

        $row = ['uuid' => 'my-id', 'stock_provider' => 'custom-provider'];
        $result = $method->invoke($import, $row);

        $this->assertEquals([
            'uuid' => 'my-id',
            'stock_provider' => 'custom-provider',
        ], $result);
    }

    public function testProcessRowCreatesNewVehicle()
    {
        Queue::fake();
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $row = [
            'uuid' => 'TEST-UUID-001',
            'registration_number' => 'AB12CDE',
            'title' => 'Ford Focus 1.0T',
            'price' => 15000,
            'colour' => 'Blue',
        ];

        $vehicle = $method->invoke($import, $row);

        $this->assertNotNull($vehicle);
        $this->assertInstanceOf(Vehicle::class, $vehicle);
        $this->assertTrue($vehicle->wasRecentlyCreated);
        $this->assertEquals('TEST-UUID-001', $vehicle->uuid);
    }

    public function testProcessRowSavesCapCode()
    {
        Queue::fake();
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $vehicle = $method->invoke($import, [
            'uuid' => 'TEST-UUID-CAP',
            'cap_id' => '12345',
            'cap_code' => 'FOFO10TSM5HPTA',
        ]);

        $this->assertNotNull($vehicle);
        $this->assertEquals('12345', $vehicle->fresh()->cap_id);
        $this->assertEquals('FOFO10TSM5HPTA', $vehicle->fresh()->cap_code);
    }

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

        Vehicle::factory()->create([
            'uuid' => 'TEST-UUID-002',
            'stock_provider' => 'default',
            'price' => 10000,
        ]);

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $row = [
            'uuid' => 'TEST-UUID-002',
            'price' => 12000,
        ];

        $vehicle = $method->invoke($import, $row);

        $this->assertNotNull($vehicle);
        $this->assertFalse($vehicle->wasRecentlyCreated);
    }

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

        Vehicle::factory()->create([
            'uuid' => 'TEST-UUID-003',
            'stock_provider' => 'original-provider',
            'price' => 10000,
        ]);

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $vehicle = $method->invoke($import, [
            'uuid' => 'TEST-UUID-003',
            'price' => 12000,
        ]);

        $this->assertNotNull($vehicle);
        $this->assertFalse($vehicle->wasRecentlyCreated);
        $this->assertEquals('original-provider', $vehicle->fresh()->stock_provider);
    }

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

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $vehicle = $method->invoke($import, [
            'uuid' => 'TEST-UUID-004',
            'price' => 9000,
        ]);

        $this->assertNotNull($vehicle);
        $this->assertTrue($vehicle->wasRecentlyCreated);
        $this->assertEquals('default', $vehicle->stock_provider);
    }

    public function testProcessRowSetsPublishFlagsIndependently()
    {
        Queue::fake();
        $this->makeSetting('sales-channels-auto-trader-enabled', true, 'boolean');

        $import = $this->makeImport();
        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $vehicle = $method->invoke($import, [
            'uuid' => 'TEST-AT-001',
            'price' => 15000,
            'publish_advert' => 'yes',
            'publish_profile' => 'no',
        ]);

        $autoTraderData = $vehicle->autoTraderData()->first();
        $this->assertNotNull($autoTraderData);
        $this->assertTrue((bool) $autoTraderData->publish_advert);
        $this->assertFalse((bool) $autoTraderData->publish_profile);
    }

    public function testProcessRowAutotraderEnabledSetsBothFlags()
    {
        Queue::fake();
        $this->makeSetting('sales-channels-auto-trader-enabled', true, 'boolean');

        $import = $this->makeImport();
        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $vehicle = $method->invoke($import, [
            'uuid' => 'TEST-AT-002',
            'price' => 15000,
            'autotrader_enabled' => 'yes',
        ]);

        $autoTraderData = $vehicle->autoTraderData()->first();
        $this->assertNotNull($autoTraderData);
        $this->assertTrue((bool) $autoTraderData->publish_advert);
        $this->assertTrue((bool) $autoTraderData->publish_profile);
    }

    public function testProcessRowDoesNotTouchAutoTraderDataWhenChannelDisabled()
    {
        Queue::fake();
        $this->makeSetting('sales-channels-auto-trader-enabled', false, 'boolean');

        $import = $this->makeImport();
        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $vehicle = $method->invoke($import, [
            'uuid' => 'TEST-AT-003',
            'price' => 15000,
            'publish_advert' => 'yes',
        ]);

        $this->assertNull($vehicle->autoTraderData()->first());
    }

    public function testProcessRowReturnsNullForEmptyRow()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $result = $method->invoke($import, []);
        $this->assertNull($result);
    }

    public function testProcessRowReturnsNullForNullRow()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $result = $method->invoke($import, null);
        $this->assertNull($result);
    }

    public function testSyncFeatures()
    {
        Queue::fake();
        $vehicle = Vehicle::factory()->create();

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'syncFeatures');
        $method->setAccessible(true);

        $method->invoke($import, $vehicle, 'Heated seats|Parking sensors|Navigation');

        $this->assertEquals(3, $vehicle->features()->count());
        $this->assertTrue($vehicle->features()->where('name', 'Heated seats')->exists());
        $this->assertTrue($vehicle->features()->where('name', 'Parking sensors')->exists());
        $this->assertTrue($vehicle->features()->where('name', 'Navigation')->exists());
    }

    public function testSyncFeaturesWithEmptyString()
    {
        Queue::fake();
        $vehicle = Vehicle::factory()->create();

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'syncFeatures');
        $method->setAccessible(true);

        $method->invoke($import, $vehicle, '');

        $this->assertEquals(0, $vehicle->features()->count());
    }

    public function testSyncStandardEquipment()
    {
        Queue::fake();
        $vehicle = Vehicle::factory()->create();

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'syncStandardEquipment');
        $method->setAccessible(true);

        $method->invoke($import, $vehicle, 'Air conditioning|Heated seats|Cruise control');

        $this->assertEquals(3, $vehicle->standardEquipment()->count());
        $this->assertTrue($vehicle->standardEquipment()->where('description', 'Air conditioning')->exists());
        $this->assertTrue($vehicle->standardEquipment()->where('description', 'Heated seats')->exists());
        $this->assertTrue($vehicle->standardEquipment()->where('description', 'Cruise control')->exists());
    }

    public function testSyncStandardEquipmentWithEmptyString()
    {
        Queue::fake();
        $vehicle = Vehicle::factory()->create();

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'syncStandardEquipment');
        $method->setAccessible(true);

        $method->invoke($import, $vehicle, '');

        $this->assertEquals(0, $vehicle->standardEquipment()->count());
    }

    public function testSyncImagesDispatchesJobForUrls()
    {
        Queue::fake();
        $vehicle = Vehicle::factory()->create();

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'syncImages');
        $method->setAccessible(true);

        $method->invoke($import, $vehicle, 'https://img.example.com/1.jpg|https://img.example.com/2.jpg');

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

    public function testSyncImagesDoesNothingForEmptyString()
    {
        Queue::fake();
        $vehicle = Vehicle::factory()->create();

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'syncImages');
        $method->setAccessible(true);

        $method->invoke($import, $vehicle, '');

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

    public function testGetMappedField()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'getMappedField');
        $method->setAccessible(true);

        $this->assertEquals('make_id', $method->invoke($import, 'make'));
        $this->assertEquals('model_id', $method->invoke($import, 'model'));
        $this->assertEquals('dealership_id', $method->invoke($import, 'dealership'));
        $this->assertEquals('fuel_type_id', $method->invoke($import, 'fuel_type'));
        $this->assertEquals('body_style_id', $method->invoke($import, 'body_style'));
        $this->assertEquals('drivetrain_id', $method->invoke($import, 'drivetrain'));
        $this->assertEquals('transmission_id', $method->invoke($import, 'transmission'));

        // Unmapped fields return as-is
        $this->assertEquals('price', $method->invoke($import, 'price'));
        $this->assertEquals('colour', $method->invoke($import, 'colour'));
        $this->assertEquals('uuid', $method->invoke($import, 'uuid'));
    }

    public function testDecodeDefaultValueLiteralValue()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $this->assertEquals('literal', $method->invoke($import, 'literal', []));
        $this->assertEquals(123, $method->invoke($import, 123, []));
        $this->assertNull($method->invoke($import, null, []));
    }

    public function testDecodeDefaultValueColumnLookup()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['make' => 'Ford', 'model' => 'Focus'];

        $result = $method->invoke($import, '{{make}}', $row);
        $this->assertEquals('Ford', $result);
    }

    public function testDecodeDefaultValueConditionEquals()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['status' => 'active'];

        $result = $method->invoke($import, '{{status=active}}', $row);
        $this->assertTrue($result);

        $result = $method->invoke($import, '{{status=inactive}}', $row);
        $this->assertFalse($result);
    }

    public function testDecodeDefaultValueConditionNotEquals()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['status' => 'active'];

        $result = $method->invoke($import, '{{status!=inactive}}', $row);
        $this->assertTrue($result);

        $result = $method->invoke($import, '{{status!=active}}', $row);
        $this->assertFalse($result);
    }

    public function testDecodeDefaultValueConditionGreaterThan()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['price' => '15000'];

        $result = $method->invoke($import, '{{price>10000}}', $row);
        $this->assertTrue($result);

        $result = $method->invoke($import, '{{price>20000}}', $row);
        $this->assertFalse($result);
    }

    public function testDecodeDefaultValueColumnNotFound()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['make' => 'Ford'];

        $result = $method->invoke($import, '{{nonexistent}}', $row);
        $this->assertNull($result);
    }

    public function testDecodeDefaultValueArithmeticAdd()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['price' => '10000'];

        $result = $method->invoke($import, '{{price+500}}', $row);
        $this->assertEquals(10500, $result);
    }

    public function testDecodeDefaultValueArithmeticSubtract()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['price' => '10000'];

        $result = $method->invoke($import, '{{price-200}}', $row);
        $this->assertEquals(9800, $result);
    }

    public function testDecodeDefaultValueArithmeticMultiply()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['price' => '10000'];

        $result = $method->invoke($import, '{{price*1.1}}', $row);
        $this->assertEquals(11000, $result);
    }

    public function testDecodeDefaultValueArithmeticDivide()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['price' => '10000'];

        $result = $method->invoke($import, '{{price/2}}', $row);
        $this->assertEquals(5000, $result);
    }

    public function testDecodeDefaultValueArithmeticDivideByZero()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['price' => '10000'];

        $result = $method->invoke($import, '{{price/0}}', $row);
        $this->assertEquals(10000, $result);
    }

    public function testDecodeDefaultValueHyphenatedColumnNotArithmetic()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['auto-trader' => 'value'];

        $result = $method->invoke($import, '{{auto-trader}}', $row);
        $this->assertEquals('value', $result);
    }

    /**
     * Import feeds supply prices as display strings such as "£ 26,750". An
     * arithmetic default value like {{buy_now_price+2000}} must parse the numeric
     * amount out of that string; a plain float cast reads it as 0 and the import
     * silently stores the arithmetic operand instead of the real price.
     */
    public function testDecodeDefaultValueArithmeticAddParsesCurrencyFormattedColumn()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['buy_now_price' => '£ 26,750'];

        $result = $method->invoke($import, '{{buy_now_price+2000}}', $row);
        $this->assertEquals(28750, $result);
    }

    /**
     * A thousands separator alone also breaks the float cast: "26,750" reads as 26.
     */
    public function testDecodeDefaultValueArithmeticAddParsesThousandsSeparatedColumn()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'decodeDefaultValue');
        $method->setAccessible(true);

        $row = ['buy_now_price' => '26,750'];

        $result = $method->invoke($import, '{{buy_now_price+2000}}', $row);
        $this->assertEquals(28750, $result);
    }

    /**
     * Extra fields configured in the import map UI are stored as is_static field
     * rows whose on_file is a UUID never present in the file row, so the value
     * always comes from the default_value expression. A currency-formatted source
     * column must still produce the correct price through the full mapping.
     */
    public function testMapBasedOnImportMapResolvesStaticPriceExpressionFromCurrencyColumn()
    {
        $import = $this->makeImport();

        $importMap = ImportMap::factory()->create();
        ImportMapField::create([
            'map_id' => $importMap->id,
            'on_file' => 'f00e9063-e6b9-45c3-bad5-2384c3043a97',
            'on_model' => 'price',
            'default_value' => '{{buy_now_price+2000}}',
            'data' => ['is_static' => true, 'save_on_update' => true],
        ]);

        $ref = new \ReflectionClass($import);
        $importMapProp = $ref->getProperty('import_map');
        $importMapProp->setAccessible(true);
        $importMapProp->setValue($import, $importMap->load('fields'));

        $method = new \ReflectionMethod($import, 'mapBasedOnImportMap');
        $method->setAccessible(true);

        $row = [
            'registration_number' => 'LC75 GJY',
            'buy_now_price' => '£ 26,500',
        ];

        $data = $method->invoke($import, $row);
        $this->assertEquals(28500, $data['price']);
    }

    /**
     * The "number" cast type must also survive a currency symbol, not only commas.
     */
    public function testCastValueNumberParsesCurrencyFormattedString()
    {
        $import = $this->makeImport();

        $importMap = ImportMap::factory()->create();
        ImportMapField::create([
            'map_id' => $importMap->id,
            'on_model' => 'price',
            'on_file' => 'buy_now_price',
            'data' => ['cast_type' => 'number'],
        ]);

        $ref = new \ReflectionClass($import);
        $importMapProp = $ref->getProperty('import_map');
        $importMapProp->setAccessible(true);
        $importMapProp->setValue($import, $importMap->load('fields'));

        $method = new \ReflectionMethod($import, 'castValue');
        $method->setAccessible(true);

        $result = $method->invoke($import, 'price', '£ 26,750', []);
        $this->assertEquals(26750, $result);
    }

    public function testGetProviderNameDefault()
    {
        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'getProviderName');
        $method->setAccessible(true);

        $this->assertEquals('default', $method->invoke($import));
    }

    public function testGetProviderNameWithImportMap()
    {
        $importMap = ImportMap::factory()->create();

        $import = $this->makeImport();

        $importMapProp = new \ReflectionProperty($import, 'import_map');
        $importMapProp->setAccessible(true);
        $importMapProp->setValue($import, $importMap);

        $method = new \ReflectionMethod($import, 'getProviderName');
        $method->setAccessible(true);

        $this->assertEquals('import-map-' . $importMap->id, $method->invoke($import));
    }

    public function testSyncAttributesDeletesObsoleteValues()
    {
        $vehicle = Vehicle::factory()->create();

        $colourAttr = VehicleAttribute::factory()->create([
            'slug' => 'colour',
            'type' => 'text',
            'model' => 'vehicle',
            'count' => 1,
        ]);
        $engineAttr = VehicleAttribute::factory()->create([
            'slug' => 'engine_size',
            'type' => 'text',
            'model' => 'vehicle',
            'count' => 1,
        ]);

        // Pre-existing value for engine_size that should be removed
        VehicleAttributeValue::factory()->create([
            'attribute_id' => $engineAttr->id,
            'owner_id' => $vehicle->id,
            'owner_type' => 'vehicle',
            'value_text' => '2.0L',
        ]);

        $import = $this->makeImport();

        $attrProp = new \ReflectionProperty($import, 'vehicleAttributes');
        $attrProp->setAccessible(true);
        $attrProp->setValue($import, [
            'colour' => $colourAttr->toArray(),
            'engine_size' => $engineAttr->toArray(),
        ]);

        $method = new \ReflectionMethod($import, 'syncAttributes');
        $method->setAccessible(true);

        // Row only provides colour — engine_size should be deleted
        $method->invoke($import, $vehicle, ['attribute.colour' => 'Red']);

        $this->assertFalse(
            VehicleAttributeValue::where('owner_id', $vehicle->id)
                ->where('attribute_id', $engineAttr->id)
                ->exists(),
            'Obsolete engine_size attribute value should be deleted'
        );
    }

    public function testSyncAttributesDeletesObsoleteValuesAcrossModels()
    {
        $vehicle = Vehicle::factory()->create();

        $vehicleAttr = VehicleAttribute::factory()->create([
            'slug' => 'colour',
            'type' => 'text',
            'model' => 'vehicle',
            'count' => 1,
        ]);
        $otherModelAttr = VehicleAttribute::factory()->create([
            'slug' => 'warranty',
            'type' => 'text',
            'model' => 'new_car',
            'count' => 1,
        ]);

        // Pre-existing value from a different model
        VehicleAttributeValue::factory()->create([
            'attribute_id' => $otherModelAttr->id,
            'owner_id' => $vehicle->id,
            'owner_type' => 'vehicle',
            'value_text' => '3 years',
        ]);

        $import = $this->makeImport();

        // vehicleAttributes is restricted to 'vehicle' model — does not include new_car attributes
        $attrProp = new \ReflectionProperty($import, 'vehicleAttributes');
        $attrProp->setAccessible(true);
        $attrProp->setValue($import, [
            'colour' => $vehicleAttr->toArray(),
        ]);

        $method = new \ReflectionMethod($import, 'syncAttributes');
        $method->setAccessible(true);

        $method->invoke($import, $vehicle, ['attribute.colour' => 'Blue']);

        $this->assertFalse(
            VehicleAttributeValue::where('owner_id', $vehicle->id)
                ->where('attribute_id', $otherModelAttr->id)
                ->exists(),
            'Attribute value from a different model should also be deleted'
        );
    }

    public function testCastToDateTimeReturnsNullForInvalidDateValue()
    {
        $importMap = ImportMap::factory()->create([
            'data' => ['date_format' => 'd/m/Y'],
        ]);

        $import = $this->makeImport();
        $import->setImportMap($importMap);

        $method = new \ReflectionMethod($import, 'castToDateTime');
        $method->setAccessible(true);

        $this->assertNull($method->invoke($import, '--/--/----'));
        $this->assertNull($method->invoke($import, 'not-a-date'));
        $this->assertNull($method->invoke($import, ''));
    }

    public function testCastToDateTimeReturnsNullForFormatMismatch()
    {
        $importMap = ImportMap::factory()->create([
            'data' => ['date_format' => 'd/m/Y'],
        ]);

        $import = $this->makeImport();
        $import->setImportMap($importMap);

        $method = new \ReflectionMethod($import, 'castToDateTime');
        $method->setAccessible(true);

        // Value is a real date but in the wrong format for d/m/Y
        $this->assertNull($method->invoke($import, '2024-06-01'));
    }

    public function testCastToDateTimeParsesValidDateWithFormat()
    {
        $importMap = ImportMap::factory()->create([
            'data' => ['date_format' => 'd/m/Y'],
        ]);

        $import = $this->makeImport();
        $import->setImportMap($importMap);

        $method = new \ReflectionMethod($import, 'castToDateTime');
        $method->setAccessible(true);

        $result = $method->invoke($import, '01/06/2024');

        $this->assertInstanceOf(\Carbon\Carbon::class, $result);
        $this->assertEquals('2024-06-01', $result->format('Y-m-d'));
    }

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

        $importMap = ImportMap::factory()->create([
            'data' => ['date_format' => 'd/m/Y'],
        ]);
        ImportMapField::create([
            'map_id' => $importMap->id,
            'on_model' => 'first_registration_date',
            'on_file' => 'first_registration_date',
            'data' => ['cast_type' => 'datetime'],
        ]);
        $importMap->load('fields');

        $import = $this->makeImport();
        $import->setImportMap($importMap);

        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);

        $vehicle = $method->invoke($import, [
            'uuid' => 'TEST-DATE-NULL-001',
            'registration_number' => 'AB12CDE',
            'first_registration_date' => '--/--/----',
        ]);

        $this->assertNotNull($vehicle);
        $this->assertNull($vehicle->fresh()->first_registration_date);
    }

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

        $missing = Vehicle::factory()->create([
            'uuid' => 'MISSING-001',
            'stock_provider' => 'default',
        ]);
        $otherProvider = Vehicle::factory()->create([
            'uuid' => 'OTHER-PROVIDER-001',
            'stock_provider' => 'another-provider',
        ]);

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);
        $kept = $method->invoke($import, [
            'uuid' => 'KEPT-001',
            'price' => 10000,
        ]);

        $import->removeMissingVehicles();

        $this->assertNull(Vehicle::find($missing->id), 'Provider vehicle absent from file should be removed');
        $this->assertTrue(Vehicle::withTrashed()->find($missing->id)->trashed(), 'Removal should be a soft delete');
        $this->assertNotNull(Vehicle::find($kept->id), 'Vehicle present in file should be kept');
        $this->assertNotNull(Vehicle::find($otherProvider->id), 'Other provider vehicles should not be touched');
    }

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

        $existing = Vehicle::factory()->create([
            'uuid' => 'EXISTING-001',
            'stock_provider' => 'default',
            'price' => 10000,
        ]);

        $import = $this->makeImport();

        $method = new \ReflectionMethod($import, 'processRow');
        $method->setAccessible(true);
        $method->invoke($import, [
            'uuid' => 'EXISTING-001',
            'price' => 12000,
        ]);

        $import->removeMissingVehicles();

        $this->assertNotNull(Vehicle::find($existing->id), 'Updated vehicle from file should be kept');
    }

    public function testRemoveMissingVehiclesSkippedWhenFileProducedNoVehicles()
    {
        $existing = Vehicle::factory()->create([
            'uuid' => 'EXISTING-002',
            'stock_provider' => 'default',
        ]);

        $import = $this->makeImport();

        $import->removeMissingVehicles();

        $this->assertNotNull(
            Vehicle::find($existing->id),
            'Removal should be skipped when no vehicles were processed from the file'
        );
    }

    public function testSetSubFieldDelimiterFromImportMap()
    {
        $importMap = ImportMap::factory()->create([
            'data' => ['sub_field_delimiter' => ';'],
        ]);

        $import = $this->makeImport();
        $import->setImportMap($importMap);

        $property = new \ReflectionProperty($import, 'subFieldDelimiter');
        $property->setAccessible(true);

        $this->assertEquals(';', $property->getValue($import));
    }
}
