<?php

namespace App\Services;

use Symfony\Component\HttpFoundation\File\UploadedFile;
use Illuminate\Validation\ValidationException;

class ImageUploadOptimizer
{
    private const WEBP_QUALITY = 88;
    private const MAX_PIXELS = 40_000_000;
    private const CONVERTIBLE_MIME_TYPES = [
        'image/jpeg',
        'image/png',
        'image/webp',
    ];

    /** @var array<int, string> */
    private array $temporaryFiles = [];

    public function optimize(UploadedFile $file): UploadedFile
    {
        if (! $file->isValid() || ! is_file($file->getPathname())) {
            return $file;
        }

        $mime = strtolower((string) $file->getMimeType());
        if (! in_array($mime, self::CONVERTIBLE_MIME_TYPES, true)) {
            return $file;
        }

        $this->assertWebpSupport();
        $this->assertReasonableDimensions($file);

        $image = match ($mime) {
            'image/jpeg' => @imagecreatefromjpeg($file->getPathname()),
            'image/png' => @imagecreatefrompng($file->getPathname()),
            'image/webp' => @imagecreatefromwebp($file->getPathname()),
            default => false,
        };

        if ($image === false) {
            throw ValidationException::withMessages([
                'file' => 'One of the uploaded images could not be decoded safely.',
            ]);
        }

        try {
            if ($mime === 'image/jpeg') {
                $image = $this->applyExifOrientation($image, $file->getPathname());
            }

            if (function_exists('imagepalettetotruecolor')) {
                @imagepalettetotruecolor($image);
            }
            imagealphablending($image, false);
            imagesavealpha($image, true);

            $temporaryPath = $this->temporaryWebpPath();
            if (! @imagewebp($image, $temporaryPath, self::WEBP_QUALITY) || ! is_file($temporaryPath)) {
                @unlink($temporaryPath);
                throw ValidationException::withMessages([
                    'file' => 'The uploaded image could not be optimized to WebP.',
                ]);
            }

            clearstatcache(true, $temporaryPath);

            // An existing WebP is already in the required format. Keep the original
            // when another encode would make it larger instead of saving space.
            if ($mime === 'image/webp' && filesize($temporaryPath) >= ($file->getSize() ?: PHP_INT_MAX)) {
                @unlink($temporaryPath);
                return $file;
            }

            $this->temporaryFiles[] = $temporaryPath;

            $baseName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
            $baseName = trim($baseName) !== '' ? $baseName : 'image';

            return new UploadedFile(
                $temporaryPath,
                $baseName.'.webp',
                'image/webp',
                UPLOAD_ERR_OK,
                true,
            );
        } finally {
            imagedestroy($image);
        }
    }

    public function cleanup(): void
    {
        foreach ($this->temporaryFiles as $path) {
            if (is_file($path)) {
                @unlink($path);
            }
        }

        $this->temporaryFiles = [];
    }

    private function assertWebpSupport(): void
    {
        if (! extension_loaded('gd') || ! function_exists('imagewebp') || ! function_exists('imagecreatefromwebp')) {
            throw ValidationException::withMessages([
                'file' => 'Image optimization is unavailable because this server does not have GD WebP support enabled.',
            ]);
        }
    }

    private function assertReasonableDimensions(UploadedFile $file): void
    {
        $size = @getimagesize($file->getPathname());
        if (! is_array($size) || ! isset($size[0], $size[1])) {
            throw ValidationException::withMessages([
                'file' => 'One of the uploaded images is invalid.',
            ]);
        }

        $width = (int) $size[0];
        $height = (int) $size[1];
        if ($width <= 0 || $height <= 0 || ($width * $height) > self::MAX_PIXELS) {
            throw ValidationException::withMessages([
                'file' => 'Uploaded images may not exceed 40 megapixels.',
            ]);
        }
    }

    private function applyExifOrientation(mixed $image, string $path): mixed
    {
        if (! function_exists('exif_read_data')) {
            return $image;
        }

        $exif = @exif_read_data($path);
        $orientation = is_array($exif) ? (int) ($exif['Orientation'] ?? 1) : 1;
        $angle = match ($orientation) {
            3 => 180,
            6 => -90,
            8 => 90,
            default => 0,
        };

        if ($angle === 0) {
            return $image;
        }

        $rotated = @imagerotate($image, $angle, 0);
        if ($rotated === false) {
            return $image;
        }

        imagedestroy($image);
        return $rotated;
    }

    private function temporaryWebpPath(): string
    {
        $base = tempnam(sys_get_temp_dir(), 'mf-webp-');
        if ($base === false) {
            throw ValidationException::withMessages([
                'file' => 'The server could not prepare the uploaded image for optimization.',
            ]);
        }

        @unlink($base);
        return $base.'.webp';
    }
}
