<?php

namespace App\Jobs;

use App\Mail\StockSyncFailedMail;
use App\Modules\Stock\ImportMap;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Mtc\MercuryDataModels\Enums\QueueProcessType;
use Mtc\MercuryDataModels\QueueProcessLog;
use Throwable;

use function tenant;

/**
 * Runs the file-based import map provider for a single tenant.
 *
 * Dispatched per tenant by the import-maps:run command so that a slow / failing remote
 * connection for one tenant cannot delay or break the others.
 */
class RunImportMapSyncTask implements ShouldQueue, ShouldBeUnique
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public $timeout = 1800;

    public $failOnTimeout = true;

    /**
     * Seconds the unique lock is held - prevents a long-running import for a tenant from
     * overlapping with the next scheduled dispatch.
     *
     * @var int
     */
    public $uniqueFor = 1860;

    public function __construct()
    {
        $this->onQueue('sync');
    }

    public function handle(): void
    {
        /** @var ImportMap $provider */
        $provider = App::make(ImportMap::class);

        if (! $provider->enabled()) {
            return;
        }

        $processLog = QueueProcessLog::start(
            QueueProcessType::StockSync,
            $provider->name(),
            ['tenant' => tenant('name'), 'trigger' => 'import-maps:run'],
        );

        try {
            // Skip the per-row search content observer during import - the StockSyncFinished
            // listener refreshes search content once at the end instead.
            Config::set('vehicles.search-content-observer-enabled', false);
            $provider->runScheduledImport();

            $stats = $provider->getImportStats();

            // Only keep a process log entry when a changed file was actually imported -
            // quiet ticks (no new/changed files) leave no trace.
            if ($provider->getProcessedFileCount() > 0 || ($stats['processed'] ?? 0) > 0) {
                $processLog->markCompleted($stats);
            } else {
                $processLog->delete();
            }
        } catch (Exception $exception) {
            $processLog->markFailed($exception->getMessage(), [
                'exception_class' => get_class($exception),
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
                'trace' => array_slice($exception->getTrace(), 0, 10),
            ]);

            if ($this->sendAlert($exception->getMessage())) {
                Mail::to(config('mail.developer_alerts'))
                    ->send(new StockSyncFailedMail($exception, tenant('id')));
            }

            Log::error('[' . tenant('id') . '] import map sync failed: ' . $exception->getMessage(), [
                'message' => $exception->getMessage(),
                'trace' => $exception->getTrace(),
            ]);
        } finally {
            Config::set('vehicles.search-content-observer-enabled', true);
        }
    }

    public function uniqueId(): string
    {
        return \app()->environment() === 'production'
            ? tenant('id') ?? 'no-tenant'
            : Str::random(32);
    }

    private function sendAlert(string $message): bool
    {
        return str_contains($message, 'Unable to connect to host')
            || str_contains($message, 'Connection timed out')
            || str_contains($message, 'sync completed with errors');
    }

    public function failed(?Throwable $exception): void
    {
        Log::error('RunImportMapSyncTask failed outside provider catch', [
            'tenant' => tenant('id'),
            'exception_class' => $exception ? get_class($exception) : 'null (worker death or reservation race)',
            'message' => $exception?->getMessage(),
            'file' => $exception?->getFile(),
            'line' => $exception?->getLine(),
        ]);
    }
}
