<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\MaxAttemptsExceededException;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Mtc\ContentManager\Contracts\Media as MediaContract;
use Mtc\ContentManager\Facades\Media;
use Mtc\ContentManager\Models\MediaUse;

class ImportImageUrlList implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public $timeout = 0;

    public $tries = 3;

    private Collection $existing;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(
        private readonly Collection $images,
        private readonly Model $owner,
        private readonly array $config = [],
    ) {
        $this->onQueue('bulk-media');
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle(): void
    {
        if ($this->shouldSkipAll()) {
            return;
        }
        try {
            $media = $this->importMedia();
            $this->assignUses($media);
            $this->removeUnnecessary();
            $this->updateMetaData($media);
        } catch (\Exception $exception) {
            Log::error('Image Bulk import failed with exception: ' . $exception->getMessage(), $exception->getTrace());
        }
    }

    public function failed(\Throwable $exception): void
    {
        if (!$exception instanceof MaxAttemptsExceededException) {
            Log::error('Image list import failed with error' . $exception->getMessage(), [
                'exception' => $exception,
                'images' => $this->images,
                'owner' => $this->owner,
                'provider' => $this->config['imageProvider'] ?? null,
            ]);
        }
    }

    private function importMedia(): Collection
    {
        $this->loadExisting();
        return $this->images->map(fn ($image) => $this->importSingleImage($image))->filter();
    }

    private function importSingleImage($image): ?Model
    {
        $existing = $this->alreadyExists($image);
        if ($existing) {
            return  $existing;
        }

        try {
            return Media::importImageFromUrl($image, '', $this->config['imageProvider'] ?? null);
        } catch (FileNotFoundException  $exception) {
            // File does not exist at url, do not log as error
            return null;
        } catch (\Exception $exception) {
            Log::warning('Failed to import image', [
                'vehicle_id' => $this->owner->id,
                'image_data' => $image,
                'error' => $exception->getMessage(),
            ]);
            return null;
        }
    }

    private function shouldSkipAll(): bool
    {
        return !empty($this->config['onlyWhenNoImages']) && $this->owner->mediaUses()->count() > 0;
    }

    private function alreadyExists($image): ?MediaContract
    {
        if (empty($this->config['imageProvider'])) {
            return null;
        }

        return $this->existing->where('source_filename', $image)->first();
    }

    private function getImagesToRemove(): Collection
    {
        $image_array = $this->images->toArray();
        $media_uses = $this->owner->mediaUses()->with('media')
            ->whereHas('media', fn($query) => $query->where('image_provider', $this->config['imageProvider'] ?? null))
            ->get();

        $not_in_list = $media_uses
            ->filter(fn($mediaUse) => !in_array($mediaUse->media->source_filename, $image_array))
            ->pluck('id');

        $duplicates = $media_uses->groupBy(fn($mediaUse) => $mediaUse->media->source_filename)
            ->filter(fn(Collection $group) => $group->count() > 1)
            ->map(fn(Collection $group) => $group->first())
            ->pluck('id');

        return $not_in_list->merge($duplicates);
    }

    private function shouldTriggerEntryUpdate(MediaContract $existing): bool
    {
        // If flag is true we always want to update
        if (!empty($this->config['updateAssigned'])) {
            return true;
        }

        // Otherwise we only want to assign if the record is not assigned to the model
        return $existing->uses
                ->where('owner_type', $this->owner->getMorphClass())
                ->where('owner_id', $this->owner->id)
                ->count() == 0;
    }

    private function loadExisting(): void
    {
        $this->existing = \Mtc\MercuryDataModels\Media::query()
            ->with('uses')
            ->where('image_provider', $this->config['imageProvider'] ?? null)
            ->whereIn('source_filename', $this->images)
            ->get();
    }

    protected function updateMetaData(Collection $media): void
    {
        $this->updateImageOrder($media);

        $media->each(function (MediaContract $media) {
            if (empty($this->config['additionalData'][$media->source_filename])) {
                return;
            }

            $additionalData = $this->config['additionalData'][$media->source_filename];
            $media->update($additionalData);
            $media->uses->each(fn (MediaUse $mediaUse) => $mediaUse->update($additionalData));
        });
    }

    private function assignUses(Collection $media): void
    {
        $mediaIds = $media
            ->filter(fn($entry) => $this->shouldTriggerEntryUpdate($entry))
            ->pluck('id')
            ->toArray();

        Media::setUsesForModel($mediaIds, $this->owner, ['primary' => $this->confg['updatePrimary'] ?? true], false);
    }

    private function removeUnnecessary(): void
    {
        if (!empty($this->config['imageProvider']) && !empty($this->owner)) {
            MediaUse::query()->whereIn('id', $this->getImagesToRemove())->delete();
        }
    }

    private function updateImageOrder(Collection $media): void
    {
        if (!empty($this->owner)) {
            Media::setUseOrdering($media->pluck('id')->flip()->toArray(), $this->owner);
        }
    }
}
