<?php

declare(strict_types=1);

namespace App\Services\Uploads;

use Illuminate\Support\Facades\File;
use Intervention\Image\ImageManager as ImageManager;
use Intervention\Image\Interfaces\ImageInterface as InterventionImageInterface;
use RuntimeException;

final class UploadDerivativesService
{
    private bool $imageAvailable = false;
    private ?ImageManager $manager = null;

    public function __construct(private readonly UploadStorageService $storage)
    {
        // Intervention Image v3 uses ImageManager; instantiate appropriate driver
        try {
            $this->manager = extension_loaded('gd') ? ImageManager::gd() : ImageManager::imagick();
            $this->imageAvailable = true;
        } catch (\Throwable $e) {
            logger()->warning('Intervention Image present but configuration failed: ' . $e->getMessage());
            $this->imageAvailable = false;
            $this->manager = null;
        }
    }

    public function storeOriginal(string $sourcePath, string $hash, ?string $originalFileName = null): string
    {
        $this->assertImageAvailable();
        // Preserve original file extension and store with filename = <hash>.<ext>
        $dir = $this->storage->ensureHashDirectory('original', $hash);

        $origExt = $this->resolveOriginalExtension($sourcePath, $originalFileName);
        $target = $dir . DIRECTORY_SEPARATOR . $hash . ($origExt !== '' ? '.' . $origExt : '');

        // Try a direct copy first (works for images and archives). If that fails,
        // fall back to re-encoding image to webp as a last resort.
        try {
            if (! File::copy($sourcePath, $target)) {
                throw new \RuntimeException('Copy failed');
            }
        } catch (\Throwable $e) {
            // Fallback: encode to webp
            $quality = (int) config('uploads.quality', 85);
            /** @var InterventionImageInterface $img */
            $img = $this->manager->read($sourcePath);
            $encoder = new \Intervention\Image\Encoders\WebpEncoder($quality);
            $encoded = (string) $img->encode($encoder);
            $target = $dir . DIRECTORY_SEPARATOR . $hash . '.webp';
            File::put($target, $encoded);
        }

        return $target;
    }

    private function resolveOriginalExtension(string $sourcePath, ?string $originalFileName): string
    {
        $fromClientName = strtolower((string) pathinfo((string) $originalFileName, PATHINFO_EXTENSION));
        if ($fromClientName !== '' && preg_match('/^[a-z0-9]{1,12}$/', $fromClientName) === 1) {
            return $fromClientName;
        }

        $fromSource = strtolower((string) pathinfo($sourcePath, PATHINFO_EXTENSION));
        if ($fromSource !== '' && $fromSource !== 'upload' && preg_match('/^[a-z0-9]{1,12}$/', $fromSource) === 1) {
            return $fromSource;
        }

        $mime = File::exists($sourcePath) ? (string) (File::mimeType($sourcePath) ?? '') : '';
        return $this->extensionFromMime($mime);
    }

    private function extensionFromMime(string $mime): string
    {
        return match (strtolower($mime)) {
            'image/jpeg', 'image/jpg' => 'jpg',
            'image/png' => 'png',
            'image/webp' => 'webp',
            'image/gif' => 'gif',
            'image/bmp' => 'bmp',
            'image/tiff' => 'tif',
            'application/zip', 'application/x-zip-compressed' => 'zip',
            'application/x-rar-compressed', 'application/vnd.rar' => 'rar',
            'application/x-7z-compressed' => '7z',
            'application/x-tar' => 'tar',
            'application/gzip', 'application/x-gzip' => 'gz',
            default => 'bin',
        };
    }

    public function generatePublicDerivatives(string $sourcePath, string $hash): array
    {
        $this->assertImageAvailable();
        $quality = (int) config('uploads.quality', 85);
        $variants = (array) config('uploads.derivatives', []);
        $written = [];

        foreach ($variants as $variant => $options) {
            $variant = (string) $variant;
            $dir = $this->storage->ensureHashDirectory($variant, $hash);
            // store derivative filename as <hash>.webp per variant directory
            $path = $dir . DIRECTORY_SEPARATOR . $hash . '.webp';

            /** @var InterventionImageInterface $img */
            $img = $this->manager->read($sourcePath);

            if (isset($options['size'])) {
                $size = (int) $options['size'];
                $out = $img->cover($size, $size);
            } else {
                $max = (int) ($options['max'] ?? 0);
                if ($max <= 0) {
                    $max = 2560;
                }

                $out = $img->scaleDown($max, $max);
            }

            $encoder = new \Intervention\Image\Encoders\WebpEncoder($quality);
            $encoded = (string) $out->encode($encoder);
            File::put($path, $encoded);
            $written[$variant] = $path;
        }

        return $written;
    }

    private function assertImageAvailable(): void
    {
        if (! $this->imageAvailable) {
            throw new RuntimeException('Intervention Image is not available.');
        }
    }
}
