<?php

namespace App\Jobs;

use App\Models\JobSourceSyncLog;
use App\Models\JobSource;
use App\Services\Jobs\JsonFeedJobImporter;
use App\Services\Jobs\XmlFeedJobImporter;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Throwable;

class SyncJobSource implements ShouldQueue
{
    use Queueable;

    public int $tries = 3;

    public int $timeout = 180;

    /**
     * Create a new job instance.
     */
    public function __construct(public JobSource $jobSource) {}

    /**
     * Execute the job.
     */
    public function handle(JsonFeedJobImporter $jsonFeedJobImporter, XmlFeedJobImporter $xmlFeedJobImporter): void
    {
        $this->jobSource->refresh();
        $startedAt = now();
        $startedMicrotime = microtime(true);

        if (! $this->jobSource->isActive()) {
            return;
        }

        try {
            $result = match ($this->jobSource->type) {
                JobSource::TYPE_API, JobSource::TYPE_JSON => $jsonFeedJobImporter->import($this->jobSource),
                JobSource::TYPE_RSS, JobSource::TYPE_XML => $xmlFeedJobImporter->import($this->jobSource),
                JobSource::TYPE_MANUAL => ['imported' => 0, 'expired' => 0],
                default => throw new \RuntimeException('No sync adapter is configured for this job source type.'),
            };

            if ($this->jobSource->type !== JobSource::TYPE_MANUAL && ! $this->jobSource->isAutomaticallySyncable()) {
                throw new \RuntimeException('No sync adapter is configured for this job source type.');
            }

            $this->markSynced($result, $startedAt, $startedMicrotime);
        } catch (Throwable $exception) {
            report($exception);

            $this->markFailed($exception->getMessage(), $startedAt, $startedMicrotime);

            throw $exception;
        }
    }

    /**
     * @param  array{imported?: int, expired?: int}|null  $result
     */
    private function markSynced(?array $result, \Illuminate\Support\Carbon $startedAt, float $startedMicrotime): void
    {
        $this->jobSource->forceFill([
            'last_synced_at' => now(),
            'last_failed_at' => null,
            'last_failure_reason' => null,
        ])->save();

        $this->jobSource->syncLogs()->create([
            'status' => JobSourceSyncLog::STATUS_SUCCESS,
            'jobs_imported' => (int) ($result['imported'] ?? 0),
            'jobs_expired' => (int) ($result['expired'] ?? 0),
            'duration_ms' => $this->durationMs($startedMicrotime),
            'message' => 'Sync completed.',
            'started_at' => $startedAt,
            'finished_at' => now(),
        ]);
    }

    private function markFailed(string $reason, \Illuminate\Support\Carbon $startedAt, float $startedMicrotime): void
    {
        $this->jobSource->forceFill([
            'last_failed_at' => now(),
            'last_failure_reason' => str($reason)->limit(1000)->toString(),
        ])->save();

        $this->jobSource->syncLogs()->create([
            'status' => JobSourceSyncLog::STATUS_FAILED,
            'jobs_imported' => 0,
            'jobs_expired' => 0,
            'duration_ms' => $this->durationMs($startedMicrotime),
            'message' => str($reason)->limit(1000)->toString(),
            'started_at' => $startedAt,
            'finished_at' => now(),
        ]);
    }

    private function durationMs(float $startedMicrotime): int
    {
        return max(0, (int) round((microtime(true) - $startedMicrotime) * 1000));
    }
}
