<?php

declare(strict_types=1);

namespace App\Helpers;

/**
 * ImageUploader — validates, resizes and saves uploaded images.
 */
final class ImageUploader
{
    private string $uploadDir;
    private string $uploadUrl;
    private int    $maxSizeKb;
    private int    $thumbW;
    private int    $thumbH;

    public function __construct(string $subFolder)
    {
        $this->uploadDir = UPLOAD_PATH . '/' . trim($subFolder, '/') . '/';
        $this->uploadUrl = UPLOAD_URL  . '/' . trim($subFolder, '/') . '/';
        $this->maxSizeKb = UPLOAD_MAX_SIZE;
        $this->thumbW    = THUMB_WIDTH;
        $this->thumbH    = THUMB_HEIGHT;
    }

    /**
     * Handle an uploaded file from $_FILES.
     *
     * @param  array<string,mixed> $file   $_FILES['field']
     * @return array{success:bool, path:string, url:string, error:string}
     */
    public function upload(array $file): array
    {
        $fail = fn(string $msg) => ['success' => false, 'path' => '', 'url' => '', 'error' => $msg];

        // ── Basic checks ──────────────────────────────────────────────────────
        if ($file['error'] !== UPLOAD_ERR_OK) {
            return $fail($this->uploadErrorMessage($file['error']));
        }

        if ($file['size'] > $this->maxSizeKb * 1024) {
            return $fail("File too large. Maximum allowed size is {$this->maxSizeKb} KB.");
        }

        // ── MIME type check (real magic bytes, not client-supplied) ───────────
        $finfo = new \finfo(FILEINFO_MIME_TYPE);
        $mime  = $finfo->file($file['tmp_name']);

        if (!in_array($mime, UPLOAD_ALLOWED_TYPES, true)) {
            return $fail('Invalid file type. Only JPG, PNG and WebP images are allowed.');
        }

        // ── Extension check ───────────────────────────────────────────────────
        $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if (!in_array($ext, UPLOAD_ALLOWED_EXT, true)) {
            return $fail('Invalid file extension.');
        }

        // ── Ensure upload directory exists ────────────────────────────────────
        if (!is_dir($this->uploadDir) && !mkdir($this->uploadDir, 0755, true)) {
            return $fail('Upload directory could not be created.');
        }

        // ── Generate unique filename ──────────────────────────────────────────
        $filename = bin2hex(random_bytes(12)) . '_' . time() . '.jpg'; // always save as JPG
        $destPath = $this->uploadDir . $filename;

        // ── Resize & convert via GD ───────────────────────────────────────────
        $result = $this->resizeAndSave($file['tmp_name'], $mime, $destPath);
        if (!$result) {
            return $fail('Image could not be processed. Ensure GD extension is enabled.');
        }

        $relativePath = trim(str_replace(UPLOAD_PATH, '', $destPath), '/');

        return [
            'success' => true,
            'path'    => $relativePath,          // stored in DB
            'url'     => $this->uploadUrl . $filename,
            'error'   => '',
        ];
    }

    /**
     * Delete an image by its relative path (as stored in DB).
     */
    public function delete(string $relativePath): void
    {
        if (!$relativePath) return;
        $full = UPLOAD_PATH . '/' . ltrim($relativePath, '/');
        if (file_exists($full)) {
            unlink($full);
        }
    }

    // ── Internals ─────────────────────────────────────────────────────────────

    private function resizeAndSave(string $tmpPath, string $mime, string $destPath): bool
    {
        $src = match ($mime) {
            'image/jpeg' => \imagecreatefromjpeg($tmpPath),
            'image/png'  => \imagecreatefrompng($tmpPath),
            'image/webp' => \imagecreatefromwebp($tmpPath),
            default      => false,
        };

        if (!$src) return false;

        $srcW = \imagesx($src);
        $srcH = \imagesy($src);

        // Calculate crop to square then scale
        $srcMin = min($srcW, $srcH);
        $srcX   = (int)(($srcW - $srcMin) / 2);
        $srcY   = (int)(($srcH - $srcMin) / 2);

        $dst = \imagecreatetruecolor($this->thumbW, $this->thumbH);

        // Preserve transparency for PNG
        \imagefill($dst, 0, 0, \imagecolorallocate($dst, 255, 255, 255));

        \imagecopyresampled($dst, $src, 0, 0, $srcX, $srcY, $this->thumbW, $this->thumbH, $srcMin, $srcMin);

        $saved = \imagejpeg($dst, $destPath, 85);

        \imagedestroy($src);
        \imagedestroy($dst);

        return $saved;
    }

    private function uploadErrorMessage(int $code): string
    {
        return match ($code) {
            UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'File exceeds maximum allowed size.',
            UPLOAD_ERR_PARTIAL   => 'File was only partially uploaded.',
            UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder.',
            UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.',
            default              => 'Unknown upload error.',
        };
    }
}
