<?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 App\VehicleType;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Queue;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleFeature;
use Mtc\MercuryDataModels\VehicleStandardEquipment;
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 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 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 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);
    }

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