<?php

declare(strict_types=1);

namespace App\Jobs\Enhance;

use App\Models\EnhanceJob;
use App\Services\Enhance\EnhanceProcessorFactory;
use App\Services\Enhance\EnhanceStorageService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Throwable;

final class ProcessEnhanceJob implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public int $tries = 2;

    public int $timeout = 300;

    public function __construct(
        private readonly int $enhanceJobId,
    ) {
        $queue = (string) config('enhance.queue', 'default');

        if ($queue !== '') {
            $this->onQueue($queue);
        }
    }

    public function handle(EnhanceProcessorFactory $factory, EnhanceStorageService $storage): void
    {
        $enhanceJob = EnhanceJob::query()->find($this->enhanceJobId);

        if (! $enhanceJob instanceof EnhanceJob) {
            return;
        }

        if (! in_array($enhanceJob->status, [EnhanceJob::STATUS_PENDING, EnhanceJob::STATUS_QUEUED, EnhanceJob::STATUS_PROCESSING, EnhanceJob::STATUS_FAILED], true)) {
            return;
        }

        $enhanceJob->forceFill([
            'status' => EnhanceJob::STATUS_PROCESSING,
            'started_at' => now(),
            'finished_at' => null,
            'error_message' => null,
        ])->save();

        Log::info('enhance.job.processing', [
            'enhance_job_id' => $enhanceJob->id,
            'user_id' => $enhanceJob->user_id,
            'engine' => $enhanceJob->engine,
        ]);

        $started = microtime(true);
        $completedExpiryDays = (int) config('enhance.lifecycle.completed_expires_after_days', 30);

        try {
            $processor = $factory->make((string) $enhanceJob->engine);
            $result = $processor->process($enhanceJob);
            $preview = $storage->createPreviewFromStoredOutput($enhanceJob, $result->disk, $result->path) ?? [];
            $outputHash = null;
            $outputContents = Storage::disk($result->disk)->get($result->path);

            if (is_string($outputContents) && $outputContents !== '') {
                $outputHash = hash('sha256', $outputContents);
            }

            $enhanceJob->forceFill([
                'status' => EnhanceJob::STATUS_COMPLETED,
                'output_disk' => $result->disk,
                'output_path' => $result->path,
                'output_hash' => $outputHash,
                'output_width' => $result->width,
                'output_height' => $result->height,
                'output_filesize' => $result->filesize,
                'output_mime' => $result->mime,
                'metadata' => array_merge($enhanceJob->metadata ?? [], $result->metadata ?? []),
                'processing_seconds' => (int) round(microtime(true) - $started),
                'finished_at' => now(),
                'expires_at' => $completedExpiryDays > 0 ? now()->addDays($completedExpiryDays) : null,
            ] + $preview)->save();

            Log::info('enhance.job.completed', [
                'enhance_job_id' => $enhanceJob->id,
                'user_id' => $enhanceJob->user_id,
                'processing_seconds' => $enhanceJob->processing_seconds,
            ]);
        } catch (Throwable $exception) {
            report($exception);

            $enhanceJob->forceFill([
                'status' => EnhanceJob::STATUS_FAILED,
                'error_message' => Str::limit($exception->getMessage(), 1000),
                'processing_seconds' => (int) round(microtime(true) - $started),
                'finished_at' => now(),
            ])->save();

            Log::warning('enhance.job.failed', [
                'enhance_job_id' => $enhanceJob->id,
                'user_id' => $enhanceJob->user_id,
                'message' => $exception->getMessage(),
            ]);

            throw $exception;
        }
    }
}