<?php

declare(strict_types=1);

final class ImageService
{
    private const ALLOWED = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif'];

    public static function inspect(string $path, int $maxBytes = 20_000_000): array
    {
        if (!is_file($path) || filesize($path) > $maxBytes) {
            throw new RuntimeException(t('service.image.missing_large'));
        }
        $info = @getimagesize($path);
        $mime = (string) ($info['mime'] ?? '');
        if (!$info || !isset(self::ALLOWED[$mime])) {
            throw new RuntimeException(t('service.image.format'));
        }
        if ((int) $info[0] * (int) $info[1] > 60_000_000) {
            throw new RuntimeException(t('service.image.dimensions'));
        }
        return [
            'mime' => $mime,
            'extension' => self::ALLOWED[$mime],
            'width' => (int) $info[0],
            'height' => (int) $info[1],
            'size' => (int) filesize($path),
        ];
    }

    public static function storeUploaded(
        string $tmp,
        string $directory,
        string $prefix = 'photo',
        string $profile = 'photo',
    ): array {
        if (!is_uploaded_file($tmp)) {
            throw new RuntimeException(t('service.image.invalid_upload'));
        }
        return self::store($tmp, $directory, $prefix, $profile, true);
    }

    public static function storeLocal(
        string $source,
        string $directory,
        string $prefix = 'photo',
        string $profile = 'photo',
    ): array {
        return self::store($source, $directory, $prefix, $profile, false);
    }

    private static function store(
        string $tmp,
        string $directory,
        string $prefix,
        string $profile,
        bool $moveSource,
    ): array {
        $source = self::inspect($tmp);
        if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) {
            throw new RuntimeException(t('service.image.directory_failed'));
        }
        $prefix = preg_replace('/[^a-z0-9_-]+/i', '_', trim($prefix)) ?: 'photo';
        $useWebp = $profile !== 'logo';
        $extension = $useWebp ? 'webp' : $source['extension'];
        $filename = $prefix . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $extension;
        $target = rtrim($directory, '/') . '/' . $filename;
        $stage = rtrim($directory, '/') . '/.' . $filename . '.part.' . $extension;
        $optimized = false;
        $binary = self::magickBinary();
        if ($binary !== null) {
            $max = $profile === 'logo' ? '1800x1800>' : '2560x2560>';
            $quality = $profile === 'medical' ? '88' : '82';
            $command = [$binary, $tmp . '[0]', '-auto-orient', '-strip', '-resize', $max];
            if ($extension === 'png') {
                $command = array_merge($command, ['-define', 'png:compression-level=9']);
            } else {
                $command = array_merge($command, ['-quality', (string) $quality]);
            }
            $command[] = $stage;
            $process = proc_open($command, [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
            if (is_resource($process)) {
                fclose($pipes[0]);
                stream_get_contents($pipes[1]);
                fclose($pipes[1]);
                stream_get_contents($pipes[2]);
                fclose($pipes[2]);
                $code = proc_close($process);
                $optimized = $code === 0 && is_file($stage) && filesize($stage) > 0 && @getimagesize($stage) !== false;
            }
        }
        if ($optimized) {
            if (!rename($stage, $target)) {
                @unlink($stage);
                throw new RuntimeException(t('service.image.optimized_failed'));
            }
        } else {
            if (is_file($stage)) {
                @unlink($stage);
            }
            $extension = $source['extension'];
            $filename = $prefix . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $extension;
            $target = rtrim($directory, '/') . '/' . $filename;
            $saved = $moveSource ? move_uploaded_file($tmp, $target) : copy($tmp, $target);
            if (!$saved) {
                throw new RuntimeException(t('service.image.save_failed'));
            }
        }
        @chmod($target, 0644);
        $stored = self::inspect($target);
        return array_merge($stored, [
            'filename' => $filename,
            'path' => $target,
            'original_size' => $source['size'],
            'optimized' => $optimized,
        ]);
    }

    private static function magickBinary(): ?string
    {
        foreach (
            ['/usr/bin/magick', '/usr/local/bin/magick', '/usr/bin/convert', '/usr/local/bin/convert']
            as $binary
        ) {
            if (is_executable($binary)) {
                return $binary;
            }
        }
        return null;
    }
}
