<?php

namespace App\Modules\ImageSync;

use App\Facades\Settings;
use App\Jobs\ImportImagesFromUrlList;
use App\Jobs\RemovePreviousAutoloadItFileVersions;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Mtc\MercuryDataModels\ApiNotification;
use Mtc\MercuryDataModels\Country;
use Mtc\MercuryDataModels\Tenant;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\Notifications\Facades\Notification;
use ZipArchive;

class AutoloadIt implements ImageSync, ToCollection, WithHeadingRow
{
    use DispatchesJobs;

    public const PROVIDER_NAME = 'autoload-it';

    public function sync(): void
    {
        $tenant = tenant('id');
        collect(Storage::disk('autoloadit-images-import')->files())
            ->filter(fn($file) => preg_match('/\.zip$/', $file))
            ->each(fn ($file) => $this->extractFile($file));

        $files = collect(Storage::disk('autoloadit-images-import')->files())
            ->filter(fn($file) => preg_match('/\.csv$/', $file));

        Tenant::query()->whereNull('suspended_at')->get()->each(function (Tenant $tenant) use ($files) {
            tenancy()->initialize($tenant);
            if (!Settings::get('image-sync-autoloadit-enabled')) {
                return;
            }

            $this->retryFromNotifications();
            $files->each(fn($file) => $this->processImageFile($file));
        });

        // return to starting tenant
        tenancy()->initialize($tenant);

        $files->each(fn($file) => $this->cleanup($file));
    }

    private function retryFromNotifications(): void
    {
        $pending_notifications = ApiNotification::query()
            ->whereNull('processed')
            ->where('provider', self::PROVIDER_NAME)
            ->get()
            ->each(fn(ApiNotification $notification) => $this->handleNotification($notification));
    }

    private function extractFile(string $file): void
    {
        $zipContent = Storage::disk('autoloadit-images-import')->get($file);

        $tempZipPath = storage_path('tempZip_' . uniqid() . '.zip');
        Storage::disk('local')->put($tempZipPath, $zipContent);

        $zip = new ZipArchive();
        if ($zip->open(Storage::disk('local')->path($tempZipPath)) === true) {
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $filename = $zip->getNameIndex($i);
                $fileStream = $zip->getStream($filename);
                if (!$fileStream) {
                    Log::debug("AutoloadIt unable to extract file: $filename");
                }
                Storage::disk('autoloadit-images-import')
                    ->put('/' . $filename, stream_get_contents($fileStream));
                fclose($fileStream);
            }
            $zip->close();
        }
        Storage::disk('autoloadit-images-import')->delete($file);
        Storage::disk('local')->delete($tempZipPath);
    }

    private function processImageFile(string $file): void
    {
        try {
            $file_contents = explode("\r\n", (string) Storage::disk('autoloadit-images-import')->get($file));
            $headings = str_getcsv(array_shift($file_contents), escape: '\\');
            $collection = collect($file_contents)
                ->map(fn($row) => collect(str_getcsv($row, escape: '\\'))
                    ->keyBy(fn($value, $index) => strtolower((string) $headings[$index])));
            $this->collection($collection);
        } catch (\Exception $exception) {
            dump($exception);
        }
    }

    public function collection(Collection $collection): void
    {
        $collection
            ->filter(fn($vehicle) => Settings::get('image-sync-autoloadit-feed-id') == ($vehicle['feed_id'] ?? ''))
            ->tap(fn(Collection $list) => Notification::addNotification(
                'success',
                'AutoloadIt image import received (vrm => image count)',
                $list->keyBy('fullregistration')->map(fn($item) => count(explode(',', (string) $item['picturerefs']))),
                'edit-vehicles',
                self::PROVIDER_NAME,
            ))
            ->each(fn($vehicle) => $this->processVehicleImages($vehicle));
    }


    private function processVehicleImages($vehicleData): void
    {
        $country = Settings::get('app-details-country') ?? config('app.default_country');
        $vrm = Country::normalizeNumberPlate($country, $vehicleData['fullregistration']);
        /** @var Vehicle|null $vehicle */
        $vehicle = Vehicle::query()
            ->where('registration_number', $vrm)
            ->first();

        $notification = ApiNotification::query()
            ->create([
                'provider' => self::PROVIDER_NAME,
                'data_model' => 'vehicle',
                'reference' => str_replace(' ', '', $vrm),
                'data' => $vehicleData,
            ]);

        if ($vehicle) {
            $images = explode(',', (string) $vehicleData['picturerefs']);

            Bus::chain([
                new ImportImagesFromUrlList(collect($images), $vehicle, false, self::PROVIDER_NAME, true),
                new RemovePreviousAutoloadItFileVersions($notification),
            ])->dispatch();
        }
    }

    private function cleanup(string $file)
    {
        if (app()->environment('production') || app()->runningUnitTests()) {
            Storage::disk('autoloadit-images-import')->move($file, str_replace('.csv', '.old', $file));
        }
    }

    private function handleNotification(ApiNotification $notification): void
    {
        /** @var Vehicle|null $vehicle */
        $vehicle = Vehicle::query()->where('vrm_condensed', $notification->reference)->latest()->first();
        if (!$vehicle) {
            return;
        }

        $images = explode(',', $notification->data['picturerefs'] ?? '');
        if ($this->hasCorrectVehicleImageCount($vehicle, $images)) {
            $notification->update(['processed' => 1]);
            return;
        }

        $this->attemptRetry($vehicle, $images);
    }

    private function hasCorrectVehicleImageCount(Vehicle $vehicle, array $images): bool
    {
        return $vehicle->mediaUses()->count() >= count($images);
    }

    private function attemptRetry(Vehicle $vehicle, array $images): void
    {
        $this->dispatch(new ImportImagesFromUrlList(collect($images), $vehicle, false, self::PROVIDER_NAME, true));
    }
}
