<?php

namespace Core\App\Services;

class UploadService
{
    private string $baseDiskPath;
    private string $baseWebPath;

    /** @var array<string,string> */
    private array $allowedImageMimes = [
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'image/webp' => 'webp',
        'image/gif' => 'gif',
        'image/x-icon' => 'ico',
        'image/vnd.microsoft.icon' => 'ico',
    ];

    public function __construct(?string $baseDiskPath = null, string $baseWebPath = '/uploads')
    {
        $this->baseDiskPath = rtrim($baseDiskPath ?: (ROOT_PATH . '/uploads'), '/');
        $this->baseWebPath = '/' . trim($baseWebPath, '/');
    }

    public function storeImage(array $file, string $directory, int $maxBytes = 5242880): string
    {
        $this->assertValidUpload($file, $maxBytes);

        $mime = $this->detectMimeType($file['tmp_name']);
        if (!isset($this->allowedImageMimes[$mime])) {
            throw new \RuntimeException('Unsupported image type.');
        }

        $imageInfo = @getimagesize($file['tmp_name']);
        if (!is_array($imageInfo) || empty($imageInfo[0]) || empty($imageInfo[1])) {
            throw new \RuntimeException('Invalid image file.');
        }

        $extension = $this->allowedImageMimes[$mime];
        return $this->moveUploadedFile($file['tmp_name'], $directory, $extension);
    }

    public function storeImages(array $files, string $directory, int $maxBytes = 5242880): array
    {
        $stored = [];
        $count = is_array($files['name'] ?? null) ? count($files['name']) : 0;

        for ($i = 0; $i < $count; $i++) {
            if (empty($files['name'][$i])) {
                continue;
            }

            $stored[] = $this->storeImage([
                'name' => $files['name'][$i] ?? '',
                'type' => $files['type'][$i] ?? '',
                'tmp_name' => $files['tmp_name'][$i] ?? '',
                'error' => $files['error'][$i] ?? UPLOAD_ERR_NO_FILE,
                'size' => $files['size'][$i] ?? 0,
            ], $directory, $maxBytes);
        }

        return $stored;
    }

    public function storeBase64Image(string $encodedData, string $directory, int $maxBytes = 10485760, ?string $declaredMime = null): string
    {
        $encodedData = trim($encodedData);
        if ($encodedData === '') {
            throw new \RuntimeException('Empty image payload.');
        }

        $binary = base64_decode($encodedData, true);
        if ($binary === false) {
            throw new \RuntimeException('Invalid base64 image payload.');
        }

        return $this->storeBinaryImage($binary, $directory, $maxBytes, $declaredMime);
    }

    public function storeBinaryImage(string $binary, string $directory, int $maxBytes = 10485760, ?string $declaredMime = null): string
    {
        if ($binary === '') {
            throw new \RuntimeException('Empty image payload.');
        }

        if (strlen($binary) > $maxBytes) {
            throw new \RuntimeException('Generated image is too large.');
        }

        $imageInfo = @getimagesizefromstring($binary);
        if (!is_array($imageInfo) || empty($imageInfo[0]) || empty($imageInfo[1])) {
            throw new \RuntimeException('Invalid generated image.');
        }

        $mime = $this->detectMimeTypeFromBinary($binary);
        if ($declaredMime) {
            $declaredMime = strtolower(trim($declaredMime));
            if ($declaredMime !== '' && isset($this->allowedImageMimes[$declaredMime])) {
                $mime = $declaredMime;
            }
        }

        if (!isset($this->allowedImageMimes[$mime])) {
            throw new \RuntimeException('Unsupported generated image type.');
        }

        $extension = $this->allowedImageMimes[$mime];
        return $this->writeBinaryFile($binary, $directory, $extension);
    }

    public function deleteByWebPath(?string $path): void
    {
        $path = trim((string)$path);
        if ($path === '' || !str_starts_with($path, $this->baseWebPath . '/')) {
            return;
        }

        $relative = ltrim(substr($path, strlen($this->baseWebPath)), '/');
        $fullPath = $this->baseDiskPath . '/' . $relative;

        if (is_file($fullPath)) {
            @unlink($fullPath);
        }
    }

    private function assertValidUpload(array $file, int $maxBytes): void
    {
        if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
            throw new \RuntimeException('Upload failed.');
        }

        if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
            throw new \RuntimeException('Invalid upload source.');
        }

        if ((int)($file['size'] ?? 0) <= 0 || (int)($file['size'] ?? 0) > $maxBytes) {
            throw new \RuntimeException('Uploaded file is too large.');
        }
    }

    private function detectMimeType(string $path): string
    {
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        if (!$finfo) {
            throw new \RuntimeException('File inspection is unavailable.');
        }

        $mime = (string)finfo_file($finfo, $path);
        finfo_close($finfo);

        return strtolower(trim($mime));
    }

    private function detectMimeTypeFromBinary(string $binary): string
    {
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        if (!$finfo) {
            throw new \RuntimeException('File inspection is unavailable.');
        }

        $mime = (string)finfo_buffer($finfo, $binary);
        finfo_close($finfo);

        return strtolower(trim($mime));
    }

    private function moveUploadedFile(string $tmpPath, string $directory, string $extension): string
    {
        $directory = trim(str_replace('..', '', str_replace('\\', '/', $directory)), '/');
        $targetDir = $this->baseDiskPath . '/' . $directory;

        if (!is_dir($targetDir) && !@mkdir($targetDir, 0755, true) && !is_dir($targetDir)) {
            throw new \RuntimeException('Unable to prepare upload directory.');
        }

        $filename = bin2hex(random_bytes(18)) . '.' . $extension;
        $targetPath = $targetDir . '/' . $filename;

        if (!move_uploaded_file($tmpPath, $targetPath)) {
            throw new \RuntimeException('Unable to save uploaded file.');
        }

        @chmod($targetPath, 0644);

        return $this->baseWebPath . '/' . $directory . '/' . $filename;
    }

    private function writeBinaryFile(string $binary, string $directory, string $extension): string
    {
        $directory = trim(str_replace('..', '', str_replace('\\', '/', $directory)), '/');
        $targetDir = $this->baseDiskPath . '/' . $directory;

        if (!is_dir($targetDir) && !@mkdir($targetDir, 0755, true) && !is_dir($targetDir)) {
            throw new \RuntimeException('Unable to prepare upload directory.');
        }

        $filename = bin2hex(random_bytes(18)) . '.' . $extension;
        $targetPath = $targetDir . '/' . $filename;

        if (@file_put_contents($targetPath, $binary) === false) {
            throw new \RuntimeException('Unable to save generated image.');
        }

        @chmod($targetPath, 0644);

        return $this->baseWebPath . '/' . $directory . '/' . $filename;
    }
}
