<?php

namespace Media\Services;

use App\Framework\Core\PathProvider;
use App\Framework\Http\UploadedFile;
use Media\Entities\Image;
use Media\Entities\ImageVariant;
use Media\Repositories\ImageRepository;
use Media\Repositories\ImageVariantRepository;
use function filesize;
use function getimagesize;
use function imageavif;
use function imagecopyresampled;
use function imagecreatefromstring;
use function imagecreatetruecolor;
use function imagedestroy;
use function imagejpeg;
use function imagesx;
use function imagesy;
use function imagewebp;

class ImageService
{
    private const array VARIANTS = [
        'thumbnail' => ['width' => 150, 'height' => 150, 'crop' => true],
        'small' => ['width' => 400, 'height' => 400, 'crop' => false],
        'medium' => ['width' => 800, 'height' => 800, 'crop' => false],
        'large' => ['width' => 1200, 'height' => 1200, 'crop' => false],
    ];

    private const array FORMATS = ['jpg', 'webp', 'avif'];

    public function __construct(
        private PathProvider $pathProvider,
        private ImageRepository $imageRepository,
        private ImageVariantRepository $variantRepository,
    ) {}

    public function uploadImage(UploadedFile $file): Image
    {
        // Validierung
        if (!$file->isValid()) {
            throw new \InvalidArgumentException('Ungültige Datei');
        }

        if (!$this->isValidImageType($file->getMimeType())) {
            throw new \InvalidArgumentException('Ungültiger Bildtyp');
        }

        // Basis-Image-Informationen erfassen
        $imageInfo = getimagesize($file->tmpName);
        $hash = \hash_file('sha256', $file->tmpName);

        // Prüfen ob ein Bild mit gleichem Hash bereits existiert
        $existingImage = $this->imageRepository->findByHash($hash);
        if ($existingImage) {
            return $existingImage; // Duplikat gefunden, vorhandenes Bild zurückgeben
        }

        // Image Entity erstellen
        $image = new Image(
            filename: $this->generateSecureFilename($file->name),
            originalFilename: $file->name,
            mimeType: $file->getMimeType(),
            fileSize: $file->size,
            width: $imageInfo[0],
            height: $imageInfo[1],
            hash: $hash,
            uploadPath: '',
        );

        // In Datenbank speichern um ID zu erhalten
        $image = $this->imageRepository->save($image);

        // Upload-Pfad setzen
        $image->uploadPath = $image->getUploadDirectory() . '/' . $image->getFilePathPattern();

        $this->imageRepository->update($image);

        // Ordnerstruktur erstellen
        $fullUploadPath = $this->pathProvider->resolvePath('storage' . $image->uploadPath);
        $this->createDirectoryStructure($fullUploadPath);

        // Original-Datei speichern
        $originalPath = $fullUploadPath . '/original.' . $this->getFileExtension($file->getMimeType());
        $file->moveTo($originalPath);

        // Varianten erstellen
        $this->createImageVariants($image, $originalPath);

        // Image in Datenbank aktualisieren
        #$this->imageRepository->update($image);

        return $image;
    }

    private function createImageVariants(Image $image, string $originalPath): void
    {
        foreach (self::VARIANTS as $variantName => $config) {
            foreach (self::FORMATS as $format) {
                $variant = $this->createImageVariant(
                    $image,
                    $originalPath,
                    $variantName,
                    $format,
                    $config
                );

                // In Datenbank speichern
                $this->variantRepository->save($variant);
            }
        }
    }

    private function createImageVariant(
        Image $image,
        string $originalPath,
        string $variantName,
        string $format,
        array $config
    ): ImageVariant {
        $outputPath = $this->pathProvider->resolvePath('storage' . $image->uploadPath . '/' . $variantName . '.' . $format);

        // Bild verarbeiten
        $resizedImage = $this->resizeImage($originalPath, $config['width'], $config['height'], $config['crop'] ?? false);
        $this->saveImageInFormat($resizedImage, $outputPath, $format);

        // Neue Dateigröße und Dimensionen ermitteln
        $newImageInfo = getimagesize($outputPath);
        $fileSize = filesize($outputPath);

        return new ImageVariant(
            imageId: $image->id,
            variant: $variantName,
            format: $format,
            width: $newImageInfo[0],
            height: $newImageInfo[1],
            fileSize: $fileSize,
            filename: $variantName . '.' . $format
        );
    }

    private function resizeImage(string $sourcePath, int $maxWidth, int $maxHeight, bool $crop = false): \GdImage
    {
        $sourceImage = imagecreatefromstring(\file_get_contents($sourcePath));
        $sourceWidth = imagesx($sourceImage);
        $sourceHeight = imagesy($sourceImage);

        if ($crop) {
            // Crop-Logik für quadratische Thumbnails
            $ratio = max($maxWidth / $sourceWidth, $maxHeight / $sourceHeight);
            $newWidth = (int)round($sourceWidth * $ratio);
            $newHeight = (int)round($sourceHeight * $ratio);

            $tempImage = imagecreatetruecolor($newWidth, $newHeight);
            imagecopyresampled($tempImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $sourceWidth, $sourceHeight);

            $finalImage = imagecreatetruecolor($maxWidth, $maxHeight);
            $cropX = (int)floor(($newWidth - $maxWidth) / 2);
            $cropY = (int)floor(($newHeight - $maxHeight) / 2);
            \imagecopy($finalImage, $tempImage, 0, 0, $cropX, $cropY, $maxWidth, $maxHeight);

            imagedestroy($tempImage);
        } else {
            // Proportionales Skalieren
            $ratio = min($maxWidth / $sourceWidth, $maxHeight / $sourceHeight);
            $newWidth = (int)round($sourceWidth * $ratio);
            $newHeight = (int)round($sourceHeight * $ratio);

            $finalImage = imagecreatetruecolor($newWidth, $newHeight);
            imagecopyresampled($finalImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $sourceWidth, $sourceHeight);
        }

        imagedestroy($sourceImage);
        return $finalImage;
    }

    private function saveImageInFormat(\GdImage $image, string $path, string $format): void
    {
        switch ($format) {
            case 'jpg':
                imagejpeg($image, $path, 85);
                break;
            case 'webp':
                imagewebp($image, $path, 85);
                break;
            case 'avif':
                if (\function_exists('imageavif')) {
                    imageavif($image, $path, 85);
                } else {
                    // Fallback auf WebP wenn AVIF nicht verfügbar
                    imagewebp($image, $path, 85);
                }
                break;
        }
        imagedestroy($image);
    }

    private function createDirectoryStructure(string $path): void
    {
        if (!is_dir($path)) {
            mkdir($path, 0755, true);
        }
    }

    private function isValidImageType(string $mimeType): bool
    {
        return in_array($mimeType, [
            'image/jpeg',
            'image/png',
            'image/gif',
            'image/webp'
        ]);
    }

    private function getFileExtension(string $mimeType): string
    {
        return match ($mimeType) {
            'image/jpeg' => 'jpg',
            'image/png' => 'png',
            'image/gif' => 'gif',
            'image/webp' => 'webp',
            default => 'jpg'
        };
    }

    private function generateSecureFilename(string $originalName): string
    {
        $extension = pathinfo($originalName, PATHINFO_EXTENSION);
        return uniqid('img_', true) . '.' . strtolower($extension);
    }

    public function getImageUrl(Image $image, string $variant = 'medium', string $format = 'jpg'): string
    {
        return '/media/' . $image->getUrlId() . '-' . $variant . '.' . $format;
    }

    public function findById(int $id): ?Image
    {
        return $this->imageRepository->findById($id);
    }

    public function getVariants(Image $image): array
    {
        return $this->variantRepository->findByImageId($image->id);
    }

    /**
     * Löst eine URL-ID zu einem Image-Objekt auf
     */
    public function resolveFromUrlId(string $urlId): ?Image
    {
        // URL-ID format: YYYYMMDDXXXXXXXX (Datum + 8-stellige ID)
        if (strlen($urlId) !== 16) {
            return null;
        }

        $idPart = substr($urlId, 8); // Die letzten 8 Zeichen
        $id = (int)ltrim($idPart, '0'); // Führende Nullen entfernen

        return $this->imageRepository->findById($id);
    }

}
