<?php

namespace App\Jobs;

use App\Contracts\StockProvider;
use App\Modules\Stock\ImportMap;
use Mtc\MercuryDataModels\Enums\QueueProcessType;
use App\Facades\Settings;
use App\IntegrationRepository;
use App\Mail\StockSyncFailedMail;
use Carbon\Carbon;
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\QueueProcessLog;
use Mtc\MercuryDataModels\Services\FinanceService;
use Mtc\MercuryDataModels\Services\FinanceServiceHelper;
use Mtc\Notifications\Facades\Notification;
use Throwable;

use function tenant;

class RunScheduledSyncTask implements ShouldQueue, ShouldBeUnique
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public $timeout = 3600;

    public $failOnTimeout = true;

    /**
     * The number of seconds after which the job's unique lock will be released.
     *
     * @var int
     */
    public $uniqueFor = 3700;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(private readonly bool $ignoreSchedule = false)
    {
        $this->onQueue('sync');
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        if ($this->isDue() !== true) {
            return;
        }
        $start = microtime(true);
        Log::debug('Starting stock sync for ' . tenant('id'));

        Config::set('vehicles.search-content-observer-enabled', false);
        foreach (['stock', 'offer-stock'] as $stockType) {
            new IntegrationRepository()->getEnabledForType($stockType)
                ->filter(fn ($integration) => !empty($integration['class']))
                ->map(fn ($integration) => App::make($integration['class']))
                ->filter(fn ($integration) => $integration instanceof StockProvider)
                // File-based import maps run on their own frequent runner (import-maps:run).
                ->reject(fn ($integration) => $integration instanceof ImportMap)
                ->filter(function (StockProvider $provider) use ($start) {
                    $this->singleTask($provider, $start);
                });
        }
        Config::set('vehicles.search-content-observer-enabled', true);

        if (FinanceServiceHelper::hasEnabledProvider() && Settings::get('finance-batch-update')) {
            new FinanceService()->requestAllVehicles();
        }
    }

    private function singleTask(StockProvider $provider, float $start): void
    {
        $processLog = QueueProcessLog::start(
            QueueProcessType::StockSync,
            $provider->name(),
            ['full_sync' => $this->isFullSync(), 'tenant' => tenant('name')]
        );

        try {
            $provider->runScheduledImport($this->isFullSync());
            $runtime = round(microtime(true) - $start);
            $stats = $provider->getImportStats();

            $processLog->markCompleted($stats);

            Notification::addNotification(
                'info',
                "Vehicle stock sync process finished. Runtime: {$runtime}s",
                $provider->name(),
                'edit-vehicles',
                "",
            );
        } 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')));
            }

            $message = '[' . tenant('id') . '] ' . $provider->name()
                . ' stock sync failed: ' . $exception->getMessage();
            Log::error($message, [
                'message' => $exception->getMessage(),
                'trace' => $exception->getTrace(),
            ]);
        }
    }

    private function isDue(): bool
    {
        if ($this->ignoreSchedule) {
            return true;
        }

        $frequency = Settings::get('stock-sync-frequency');
        $now = Carbon::now();
        $duringWorkWindow = $now->hour >= 6 && $now->hour <= 22;
        return match ($frequency) {
            'daily' => $now->hour === 8,
            'every-two-hours' => ($now->hour % 2 === 0) && $duringWorkWindow,
            'every-hour' => $duringWorkWindow,
            default => $now->hour === 9 || $now->hour === 17,
        };
    }

    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('RunScheduledSyncTask 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(),
        ]);
    }

    private function isFullSync(): bool
    {
        if ($this->ignoreSchedule) {
            return true;
        }

        $frequency = Settings::get('stock-sync-frequency');
        $now = Carbon::now();
        // Daily triggers once so it is always full sync
        // Twice daily we trigger on first sync of the day
        // Every hour and every 2 hours we do full sync once in night period, rest of the time allow quick sync
        return match ($frequency) {
            'daily' => true,
            'every-two-hours',
            'every-hour' => $now->hour === 6,
            default => $now->hour === 9,
        };
    }
}
