<?php

namespace App\Services;

use App\Jobs\ConvertPowerPointJob;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use InvalidArgumentException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use ZipArchive;

class FileConversionService
{
    private const MAX_FILE_SIZE_BYTES = 52428800;

    private const SUPPORTED_EXTENSIONS = ['png', 'jpg', 'jpeg', 'ppt', 'pptx', 'zip'];

    private const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg'];

    private const POWERPOINT_EXTENSIONS = ['ppt', 'pptx'];

    public function convertImage(UploadedFile|string|SplFileInfo $file): array
    {
        $sourcePath = $this->resolvePath($file);
        $extension = $this->resolveExtension($file, $sourcePath);
        $this->assertSupported($extension);

        if (! in_array($extension, self::IMAGE_EXTENSIONS, true)) {
            throw new InvalidArgumentException('Nur Bilddateien koennen mit convertImage verarbeitet werden.');
        }

        $this->assertSize($file, $sourcePath);

        $filename = Str::uuid()->toString().'.jpg';
        $relativePath = 'slides/'.$filename;
        $targetPath = Storage::disk('public')->path($relativePath);
        Storage::disk('public')->makeDirectory('slides');
        $this->ensureDirectory(dirname($targetPath));

        $manager = $this->createImageManager();
        $image = $manager->read($sourcePath);

        $originalWidth = $image->width();
        $originalHeight = $image->height();
        $warnings = $this->checkImageDimensions($originalWidth, $originalHeight);

        $image->contain(1920, 1080, '000000', 'center');
        $image->save($targetPath, quality: 90);

        $thumbnailPath = $this->generateThumbnail($relativePath);

        return [
            'filename' => $relativePath,
            'thumbnail' => $thumbnailPath,
            'warnings' => $warnings,
        ];
    }

    public function convertPowerPoint(UploadedFile|string|SplFileInfo $file): string
    {
        $sourcePath = $this->resolvePath($file);
        $extension = $this->resolveExtension($file, $sourcePath);
        $this->assertSupported($extension);

        if (! in_array($extension, self::POWERPOINT_EXTENSIONS, true)) {
            throw new InvalidArgumentException('Nur PPT/PPTX-Dateien sind hier erlaubt.');
        }

        $this->assertSize($file, $sourcePath);

        $jobId = Str::uuid()->toString();
        ConvertPowerPointJob::dispatch($sourcePath, $jobId);

        return $jobId;
    }

    public function processZip(UploadedFile|string|SplFileInfo $file): array
    {
        $sourcePath = $this->resolvePath($file);
        $extension = $this->resolveExtension($file, $sourcePath);
        $this->assertSupported($extension);

        if ($extension !== 'zip') {
            throw new InvalidArgumentException('processZip erwartet eine ZIP-Datei.');
        }

        $this->assertSize($file, $sourcePath);

        $zip = new ZipArchive;
        if ($zip->open($sourcePath) !== true) {
            throw new InvalidArgumentException('ZIP-Datei konnte nicht geoeffnet werden.');
        }

        $extractDir = storage_path('app/temp/zip-'.Str::uuid()->toString());
        if (! is_dir($extractDir) && ! mkdir($extractDir, 0775, true) && ! is_dir($extractDir)) {
            throw new InvalidArgumentException('Temporaires ZIP-Verzeichnis konnte nicht erstellt werden.');
        }

        $zip->extractTo($extractDir);
        $zip->close();

        $results = [];

        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($extractDir, RecursiveDirectoryIterator::SKIP_DOTS)
        );

        foreach ($iterator as $entry) {
            if (! $entry instanceof SplFileInfo || ! $entry->isFile()) {
                continue;
            }

            $entryPath = $entry->getRealPath();
            if ($entryPath === false) {
                continue;
            }

            $entryExtension = $this->extensionFromPath($entryPath);
            if (! in_array($entryExtension, self::SUPPORTED_EXTENSIONS, true)) {
                continue;
            }

            if (in_array($entryExtension, self::IMAGE_EXTENSIONS, true)) {
                $results[] = $this->convertImage($entryPath);

                continue;
            }

            if (in_array($entryExtension, self::POWERPOINT_EXTENSIONS, true)) {
                $results[] = ['job_id' => $this->convertPowerPoint($entryPath)];

                continue;
            }

            $results = [...$results, ...$this->processZip($entryPath)];
        }

        $this->deleteDirectory($extractDir);

        return $results;
    }

    public function generateThumbnail(string $path): string
    {
        $absolutePath = str_starts_with($path, DIRECTORY_SEPARATOR)
            ? $path
            : Storage::disk('public')->path($path);

        if (! is_file($absolutePath)) {
            throw new InvalidArgumentException('Datei fuer Thumbnail nicht gefunden.');
        }

        $filename = pathinfo($absolutePath, PATHINFO_FILENAME).'.jpg';
        $thumbnailRelativePath = 'slides/thumbnails/'.$filename;
        $thumbnailAbsolutePath = Storage::disk('public')->path($thumbnailRelativePath);
        Storage::disk('public')->makeDirectory('slides/thumbnails');
        $this->ensureDirectory(dirname($thumbnailAbsolutePath));

        $manager = $this->createImageManager();
        $canvas = $manager->create(320, 180)->fill('000000');
        $image = $manager->read($absolutePath);
        $image->scaleDown(width: 320, height: 180);
        $canvas->place($image, 'center');
        $canvas->save($thumbnailAbsolutePath, quality: 85);

        return $thumbnailRelativePath;
    }

    private function resolvePath(UploadedFile|string|SplFileInfo $file): string
    {
        if ($file instanceof UploadedFile) {
            $path = $file->getRealPath();
            if ($path === false || ! is_file($path)) {
                throw new InvalidArgumentException('Upload-Datei ist ungueltig.');
            }

            return $path;
        }

        if ($file instanceof SplFileInfo) {
            $path = $file->getRealPath();
            if ($path === false) {
                throw new InvalidArgumentException('Dateipfad ist ungueltig.');
            }

            return $path;
        }

        if (! is_file($file)) {
            throw new InvalidArgumentException('Datei wurde nicht gefunden.');
        }

        return $file;
    }

    private function assertSupported(string $extension): void
    {
        if (! in_array($extension, self::SUPPORTED_EXTENSIONS, true)) {
            throw new InvalidArgumentException('Dateityp wird nicht unterstuetzt.');
        }
    }

    private function extensionFromPath(string $path): string
    {
        return strtolower((string) pathinfo($path, PATHINFO_EXTENSION));
    }

    private function resolveExtension(UploadedFile|string|SplFileInfo $file, string $sourcePath): string
    {
        if ($file instanceof UploadedFile) {
            return strtolower($file->getClientOriginalExtension());
        }

        return $this->extensionFromPath($sourcePath);
    }

    private function assertSize(UploadedFile|string|SplFileInfo $file, string $resolvedPath): void
    {
        $size = $file instanceof UploadedFile
            ? ($file->getSize() ?? 0)
            : (filesize($resolvedPath) ?: 0);

        if ($size > self::MAX_FILE_SIZE_BYTES) {
            throw new InvalidArgumentException('Datei ist groesser als 50MB.');
        }
    }

    private function deleteDirectory(string $directory): void
    {
        if (! is_dir($directory)) {
            return;
        }

        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::CHILD_FIRST
        );

        foreach ($iterator as $item) {
            if (! $item instanceof SplFileInfo) {
                continue;
            }

            if ($item->isDir()) {
                @rmdir($item->getPathname());

                continue;
            }

            @unlink($item->getPathname());
        }

        @rmdir($directory);
    }

    /** @return string[] */
    private function checkImageDimensions(int $width, int $height): array
    {
        $warnings = [];

        $isExactOrLarger16by9 = $width >= 1920 && $height >= 1080
            && abs($width / $height - 16 / 9) < 0.01;

        if (! $isExactOrLarger16by9) {
            $warnings[] = 'Das Bild hat nicht das optimale Seitenverhältnis von 16:9. '
                .'Für die beste Darstellung verwende bitte Bilder mit exakt 1920×1080 Pixeln. '
                .'Das Bild wurde trotzdem verarbeitet, es können aber schwarze Ränder entstehen.';
        }

        if ($width < 1920 || $height < 1080) {
            $warnings[] = "Das Bild ({$width}×{$height}) ist kleiner als 1920×1080 und wurde hochskaliert. "
                .'Dadurch kann die Qualität schlechter sein. '
                .'Lade am besten Bilder mit mindestens 1920×1080 Pixeln hoch.';
        }

        return $warnings;
    }

    private function ensureDirectory(string $directory): void
    {
        if (is_dir($directory)) {
            return;
        }

        if (! mkdir($directory, 0775, true) && ! is_dir($directory)) {
            throw new InvalidArgumentException('Zielverzeichnis konnte nicht erstellt werden.');
        }
    }

    private function createImageManager(): mixed
    {
        $managerClass = implode('\\', ['Intervention', 'Image', 'ImageManager']);
        $driverClass = implode('\\', ['Intervention', 'Image', 'Drivers', 'Gd', 'Driver']);

        return new $managerClass(new $driverClass);
    }
}
