<?php

namespace Tests\Tenant;

use App\Facades\Settings;
use App\Jobs\RunImportMapSyncTask;
use App\Models\ImportMap as ImportMapModel;
use App\Models\ImportMapFile;
use App\Modules\Stock\ImportMap;
use Carbon\Carbon;
use Illuminate\Support\Facades\Queue;
use Mtc\MercuryDataModels\QueueProcessLog;
use Mtc\MercuryDataModels\Vehicle;
use Tests\TenantTestCase;

class ImportMapStockTest 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,
        ]);
    }

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

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

    public function testName()
    {
        $this->assertEquals('Remote stock file', (new ImportMap())->name());
    }

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

    public function testIsImportableFile()
    {
        $importMap = new ImportMap();
        $method = new \ReflectionMethod(ImportMap::class, 'isImportableFile');
        $method->setAccessible(true);

        // Set up import_map property
        $importMapProp = new \ReflectionProperty(ImportMap::class, 'import_map');
        $importMapProp->setAccessible(true);
        $importMapProp->setValue($importMap, ImportMapModel::factory()->create([
            'data' => ['file_name' => null],
        ]));

        $this->assertTrue($method->invoke($importMap, 'stock.csv'));
        $this->assertTrue($method->invoke($importMap, 'data.xls'));
        $this->assertTrue($method->invoke($importMap, 'vehicles.xlsx'));
        $this->assertTrue($method->invoke($importMap, 'archive.zip'));
        $this->assertFalse($method->invoke($importMap, 'readme.txt'));
        $this->assertFalse($method->invoke($importMap, 'image.jpg'));
        $this->assertFalse($method->invoke($importMap, 'script.php'));
    }

    public function testIsImportableFileWithSpecificFilename()
    {
        $importMap = new ImportMap();
        $method = new \ReflectionMethod(ImportMap::class, 'isImportableFile');
        $method->setAccessible(true);

        $importMapProp = new \ReflectionProperty(ImportMap::class, 'import_map');
        $importMapProp->setAccessible(true);
        $importMapProp->setValue($importMap, ImportMapModel::factory()->create([
            'data' => ['file_name' => 'specific-stock.csv'],
        ]));

        $this->assertTrue($method->invoke($importMap, 'specific-stock.csv'));
        $this->assertFalse($method->invoke($importMap, 'other-stock.csv'));
    }

    public function testIsSpecifiedFileFormat()
    {
        $importMap = new ImportMap();
        $method = new \ReflectionMethod(ImportMap::class, 'isSpecifiedFileFormat');
        $method->setAccessible(true);

        $this->assertTrue($method->invoke($importMap, 'file.csv'));
        $this->assertTrue($method->invoke($importMap, 'file.xls'));
        $this->assertTrue($method->invoke($importMap, 'file.xlsx'));
        $this->assertTrue($method->invoke($importMap, 'file.zip'));
        $this->assertFalse($method->invoke($importMap, 'file.txt'));
        $this->assertFalse($method->invoke($importMap, 'file.json'));
        $this->assertFalse($method->invoke($importMap, 'file.xml'));
    }

    public function testGetBackupFileName()
    {
        $importMap = new ImportMap();
        $method = new \ReflectionMethod(ImportMap::class, 'getBackupFileName');
        $method->setAccessible(true);

        $importMapModel = ImportMapModel::factory()->create();
        $importMapProp = new \ReflectionProperty(ImportMap::class, 'import_map');
        $importMapProp->setAccessible(true);
        $importMapProp->setValue($importMap, $importMapModel);

        $result = $method->invoke($importMap, 'stock.csv');
        $this->assertEquals($importMapModel->id . '_import.csv.bck', $result);

        $result = $method->invoke($importMap, 'data.xlsx');
        $this->assertEquals($importMapModel->id . '_import.xlsx.bck', $result);
    }

    public function testLoadImportMapsFiltersAutomated()
    {
        // Create automated and non-automated import maps
        ImportMapModel::factory()->create([
            'data' => ['automate' => true, 'protocol' => 'ftp', 'host' => 'example.com', 'username' => 'u', 'password' => 'p'],
        ]);
        ImportMapModel::factory()->create([
            'data' => ['automate' => false],
        ]);
        ImportMapModel::factory()->create([
            'data' => [],
        ]);

        $importMap = new ImportMap();
        $method = new \ReflectionMethod(ImportMap::class, 'loadImportMaps');
        $method->setAccessible(true);

        $result = $method->invoke($importMap);

        $this->assertEquals(1, $result->count());
    }

    public function testRunScheduledImportWithNoAutomatedMaps()
    {
        Queue::fake();
        $this->makeSetting('stock-file-enabled', true);

        // No import maps at all
        $importMap = new ImportMap();
        $importMap->runScheduledImport();

        // Should not throw an error
        $this->assertEquals(0, Vehicle::query()->count());
    }

    public function testAggregateStats()
    {
        $importMap = new ImportMap();
        $aggregateMethod = new \ReflectionMethod(ImportMap::class, 'aggregateStats');
        $aggregateMethod->setAccessible(true);
        $resetMethod = new \ReflectionMethod(ImportMap::class, 'resetStats');
        $resetMethod->setAccessible(true);

        $resetMethod->invoke($importMap);

        $aggregateMethod->invoke($importMap, ['created' => 3, 'updated' => 5, 'failed' => 1, 'processed' => 9]);

        $stats = $importMap->getImportStats();
        $this->assertEquals(3, $stats['created']);
        $this->assertEquals(5, $stats['updated']);
        $this->assertEquals(1, $stats['failed']);
    }

    public function testAggregateStatsAccumulates()
    {
        $importMap = new ImportMap();
        $aggregateMethod = new \ReflectionMethod(ImportMap::class, 'aggregateStats');
        $aggregateMethod->setAccessible(true);
        $resetMethod = new \ReflectionMethod(ImportMap::class, 'resetStats');
        $resetMethod->setAccessible(true);

        $resetMethod->invoke($importMap);

        $aggregateMethod->invoke($importMap, ['created' => 2, 'updated' => 3, 'failed' => 0, 'processed' => 5]);
        $aggregateMethod->invoke($importMap, ['created' => 1, 'updated' => 4, 'failed' => 2, 'processed' => 7]);

        $stats = $importMap->getImportStats();
        $this->assertEquals(3, $stats['created']);
        $this->assertEquals(7, $stats['updated']);
        $this->assertEquals(2, $stats['failed']);
    }

    public function testFileNeedsImportProcessesNewFileWithoutRecord()
    {
        config(['automotive.import_map_min_file_age' => 180]);
        $map = ImportMapModel::factory()->create();

        $this->assertTrue(
            $this->invokeFileNeedsImport($map, 'stock.csv', Carbon::now()->subMinutes(10))
        );
    }

    public function testFileNeedsImportSkipsInFlightFile()
    {
        config(['automotive.import_map_min_file_age' => 180]);
        $map = ImportMapModel::factory()->create();

        // Modified a minute ago - younger than the 180s in-flight guard, so it is skipped.
        $this->assertFalse(
            $this->invokeFileNeedsImport($map, 'stock.csv', Carbon::now()->subMinute())
        );
    }

    public function testFileNeedsImportSkipsUnchangedFile()
    {
        config(['automotive.import_map_min_file_age' => 180]);
        $map = ImportMapModel::factory()->create();
        $modified = Carbon::now()->subMinutes(10)->startOfSecond();

        ImportMapFile::create([
            'map_id' => $map->id,
            'file_name' => 'stock.csv',
            'file_modified_at' => $modified,
            'imported_at' => Carbon::now()->subMinutes(9),
        ]);

        // Same modified time as last import - nothing changed, so it is skipped.
        $this->assertFalse($this->invokeFileNeedsImport($map, 'stock.csv', $modified));
    }

    public function testFileNeedsImportReprocessesNewerFile()
    {
        config(['automotive.import_map_min_file_age' => 180]);
        $map = ImportMapModel::factory()->create();

        ImportMapFile::create([
            'map_id' => $map->id,
            'file_name' => 'stock.csv',
            'file_modified_at' => Carbon::now()->subDay(),
            'imported_at' => Carbon::now()->subDay(),
        ]);

        $this->assertTrue(
            $this->invokeFileNeedsImport($map, 'stock.csv', Carbon::now()->subMinutes(10))
        );
    }

    public function testFileNeedsImportProcessesWhenModifiedTimeUnavailable()
    {
        $map = ImportMapModel::factory()->create();

        // Null modified time (driver could not report one) falls back to importing.
        $this->assertTrue($this->invokeFileNeedsImport($map, 'stock.csv', null));
    }

    public function testRecordImportedFileUpsertsSingleRow()
    {
        $map = ImportMapModel::factory()->create();
        $provider = $this->providerWithMap($map);

        $method = new \ReflectionMethod(ImportMap::class, 'recordImportedFile');
        $method->setAccessible(true);

        $method->invoke($provider, 'stock.csv', Carbon::now()->subMinutes(20)->startOfSecond());

        $this->assertDatabaseHas('import_map_files', [
            'map_id' => $map->id,
            'file_name' => 'stock.csv',
        ]);
        $this->assertEquals(1, ImportMapFile::where('map_id', $map->id)->count());

        // A second import of the same file updates the existing row rather than creating another.
        $newer = Carbon::now()->subMinutes(5)->startOfSecond();
        $method->invoke($provider, 'stock.csv', $newer);

        $this->assertEquals(1, ImportMapFile::where('map_id', $map->id)->count());
        $record = ImportMapFile::where('map_id', $map->id)->where('file_name', 'stock.csv')->first();
        $this->assertEquals($newer->toDateTimeString(), $record->file_modified_at->toDateTimeString());
    }

    public function testImportMapsRunCommandDispatchesJobWhenEnabled()
    {
        Queue::fake();
        $this->makeSetting('stock-file-enabled', true);

        $this->artisan('import-maps:run')->assertExitCode(0);

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

    public function testImportMapsRunCommandDoesNotDispatchWhenDisabled()
    {
        Queue::fake();
        $this->makeSetting('stock-file-enabled', false);

        $this->artisan('import-maps:run')->assertExitCode(0);

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

    public function testSyncJobReturnsEarlyWhenDisabled()
    {
        $this->makeSetting('stock-file-enabled', false);

        new RunImportMapSyncTask()->handle();

        $this->assertEquals(0, QueueProcessLog::query()->count());
    }

    public function testSyncJobWritesNoProcessLogWhenNothingImported()
    {
        $this->makeSetting('stock-file-enabled', true);

        // No automated import maps configured, so no files are processed.
        new RunImportMapSyncTask()->handle();

        $this->assertEquals(0, QueueProcessLog::query()->count());
    }

    public function testProcessedFileCountStartsAtZero()
    {
        $provider = new ImportMap();
        $this->assertEquals(0, $provider->getProcessedFileCount());

        // With no automated maps the run processes no files and the count stays at zero.
        $provider->runScheduledImport();
        $this->assertEquals(0, $provider->getProcessedFileCount());
    }

    private function providerWithMap(ImportMapModel $map): ImportMap
    {
        $provider = new ImportMap();
        $property = new \ReflectionProperty(ImportMap::class, 'import_map');
        $property->setAccessible(true);
        $property->setValue($provider, $map);

        return $provider;
    }

    private function invokeFileNeedsImport(ImportMapModel $map, string $file, ?Carbon $modifiedAt): bool
    {
        $method = new \ReflectionMethod(ImportMap::class, 'fileNeedsImport');
        $method->setAccessible(true);

        return $method->invoke($this->providerWithMap($map), $file, $modifiedAt);
    }
}
