<?php

namespace Mnv\Core\Uploads;

use Mnv\Core\Config;
use Mnv\Core\Filesystem\Filesystem;
use Mnv\Core\Filesystem\FilesystemFactory;
use Mnv\Core\Locale\I18N;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyUploadedFile;

/**
 * Class Uploader
 * @package Mnv\Core\Uploads
 */
class Uploader extends Filesystem
{

    const IMAGETYPE_GIF = 1;
    const IMAGETYPE_JPEG = 2;
    const IMAGETYPE_PNG = 3;
    const IMAGETYPE_WEBP = 4;

    protected $config = [];
    protected $options = [];

    /** @var array  */
    public array $response = [];

    /** @var SymfonyUploadedFile|mixed  */
    protected SymfonyUploadedFile $file;

    /** @var ImageGenerator|ImagineGenerator */
    protected $generator;

    /** @var array  */
    private array $uploaded_file = [];

//    protected function error_messages($key)
//    {
//        $error_messages = array(
//            1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
//            2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
//            3 => 'The uploaded file was only partially uploaded',
//            4 => 'No file was uploaded',
//            6 => 'Missing a temporary folder',
//            7 => 'Failed to write file to disk',
//            8 => 'A PHP extension stopped the file upload',
//            'post_max_size' => 'The uploaded file exceeds the post_max_size directive in php.ini', // Загруженный файл превышает параметр post_max_size или upload_max_filesize в php.ini
//            'accept_file_types'     => 'Filetype not allowed',
//            'max_number_of_files'   => 'Maximum number of files exceeded',
//            'abort'         => 'File upload aborted',
//            'upload_max_filesize'   => I18N::locale("Загружаемый файл превышает параметр «UPLOAD_MAX_FILESIZE» или «POST_MAX_SIZE», проверьте настройки сервера!", "Yuklab olinadigan fayl «UPLOAD_MAX_FILESIZE» yoki «POST_MAX_SIZE» parametrlaridan oshib ketadi, server sozlamalarini tekshiring!", "The uploaded file exceeds the «UPLOAD_MAX_FILESIZE» or «POST_MAX_SIZE» parameter, check the server settings!"),
//            'file_size_empty'       => I18N::locale("Загружаемый файл пустой, либо к нему невозможно получить доступ.", "Yuklab olinadigan fayl bo'sh yoki unga kirish mumkin emas.", "The downloaded file is empty, or it cannot be accessed."),
//            'max_file_size'         => I18N::locale("Загруженный файл превышает параметр «MAX_FILE_SIZE», указанный в HTML-форме", "Yuklangan fayl HTML shaklida ko'rsatilgan «MAX_FILE_SIZE» parametridan oshib ketadi", "The uploaded file exceeds the «MAX_FILE_SIZE» parameter specified in the HTML form"),
//            'min_file_size'         => I18N::locale("Файл слишком мал", "Fayl juda kichik", "File is too small"),
//            'max_width_height'      => I18N::locale("Изображение превышает максимальную ширину или высоту", "Rasm maksimal kenglik yoki balandlikdan oshadi", "Image exceeds maximum width or height"),
//            'min_width_height'      => I18N::locale("Для изображения требуется минимальная ширина или высота", "Rasm minimal kenglik yoki balandlikni talab qiladi", "Image requires a minimum width or height"),
//            'root_path_error'       => I18N::locale("Нельзя загружать файлы в корневую директорию, нужно создать новую директорию или выбрать из существующих директорий.", "Siz fayllarni ildiz katalogiga yuklay olmaysiz, yangi katalog yaratishingiz yoki mavjud kataloglardan tanlashingiz kerak.", "You cannot upload files to the root directory, you need to create a new directory or select from existing directories."),
//            'duplicate_file'        => I18N::locale("Файл с данным именем уже существует на сервере, измените имя загружаемого файла на другое.", "Ushbu nomdagi fayl allaqachon serverda mavjud, yuklab olingan fayl nomini boshqasiga o'zgartiring.", "A file with this name already exists on the server, change the name of the downloaded file to another one."),
//            'no_file_was_uploaded'  => I18N::locale("Файл не удалось загрузить, загружаемый файл превышает параметр «UPLOAD_MAX_FILESIZE» или «POST_MAX_SIZE», проверьте настройки сервера!", "Faylni yuklab bo'lmadi, yuklab olinadigan fayl «UPLOAD_MAX_FILESIZE» yoki «POST_MAX_SIZE» parametrlaridan oshib ketdi, server sozlamalarini tekshiring!", "The file could not be uploaded, the uploaded file exceeds the «UPLOAD_MAX_FILESIZE» or «POST_MAX_SIZE» parameter, check the server settings!"),
//            'unable_upload_file'    => I18N::locale("Невозможно загрузить файл на сервер", "Rasm qo'shildi!", "Image added!")
//        );
//
//        return $error_messages[$key] ?? null;
//    }

    public function __construct($options, $realPath, $path, $managerId)
    {
        $this->options = include __DIR__ . '/config/config.inc.php';
        if (!empty($options)) {
            $this->options = $options + $this->options;
        }

        $this->options['max_file_size'] = Config::getValue('max_file_size') * 1024 * 1024;
        $this->options['max_up_size']   = Config::getValue('max_up_size') * 1024 * 1024;

        $this->realPath = $realPath;
        $this->path     = $path;

        // TODO: доработать `request->file()`
        if (request()->hasFile('file')) {
            $this->file = request()->files->get('file', '');

            $this->uploaded_file['name']      = $this->file->getClientOriginalName();
            $this->uploaded_file['tmp_name']  = $this->file->getPathname();
            $this->uploaded_file['mimeType']  = $this->file->getClientMimeType();
            $this->uploaded_file['size']      = $this->file->getSize();
            $this->uploaded_file['extension'] = $this->file->getClientOriginalExtension();
            $this->uploaded_file['error']     = $this->file->getError();
        }

        if (Config::getValue('image_generation') == 'imagine') {
            $this->generator = FilesystemFactory::imagineGenerator($this->realPath, $this->path, $managerId);
        } else {
            $this->generator = FilesystemFactory::imageGenerator($this->realPath, $this->path, $managerId);
        }
    }


    /**
     * Валидация загружаемого файла
     * @return bool
     */
    public function validate(): bool
    {

        if (!empty($this->file)) {

            if (!empty($this->file->getError())) {
                $this->response = array('status' => 500, 'message' => $this->file->getError(), 'error' => $this->file->getError(), 'type' => 'error');
                return false;
            }


            if ($this->file->getSize() > SymfonyUploadedFile::getMaxFilesize()) {
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:28'), 'error' => 'upload_max_filesize', 'type' => 'error');
                return false;
            }

            if (!$this->file->getSize()) {
                /** Загружаемый файл пустой, либо к нему невозможно получить доступ. */
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:13'), 'error' => 'file_size_empty', 'type' => 'error');
                return false;
            }
            /** Загруженный файл превышает параметр MAX_FILE_SIZE, указанный в HTML-форме */
            if ($this->options['max_file_size'] && $this->file->getSize() > ($this->options['max_file_size'])) {
                /** Слишком большой размер файла */
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:26'), 'error' => 'max_file_size', 'type' => 'error');
                return false;
            }

            /** Файл слишком мал */
            if ($this->options['min_file_size'] && $this->file->getSize() < $this->options['min_file_size']) {
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:25'), 'error' => 'min_file_size', 'type' => 'error');
                return false;
            }

            // Для работы с изображениями (gif|jpe?g|png|webp)
            if ($this->is_valid_image_file()) {

                list($img_width, $img_height) = $this->get_image_size();

                /** Слишком большое изображение */
                if ($this->options['max_up_size'] && $this->file->getSize() > ($this->options['max_up_size'])) {
                    $this->response = array('status' => 500, 'message' => lang('fileManager:errors:15'), 'error' => 'max_up_size', 'type' => 'error');
                    return false;
                }

                /** Изображение превышает максимальную ширину */
                if (($this->options['max_width'] && $img_width > $this->options['max_width']) || ($this->options['max_height'] && $img_height > $this->options['max_height'])) {
                    $this->response = array('status' => 500, 'message' => lang('fileManager:errors:21') . ': ' . $this->options['max_width'] . 'px', 'error' => 'max_width_height', 'type' => 'error');
                    return false;
                }
//
                /** Для изображения требуется минимальная ширина */
                if (($this->options['min_width'] && $img_width < $this->options['min_width']) || ($this->options['min_height'] && $img_height < $this->options['min_height'])) {
                    $this->response = array('status' => 500, 'message' => lang('fileManager:errors:23'), 'error' => 'min_width_height', 'type' => 'error');
                    return false;
                }
            }

            if ($this->path == '') {
                /** Нельзя загружать файлы в корневую директорию */
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:18'), 'error' => 'root_path_error', 'type' => 'error');
                return false;
            }

//            if (preg_match('/[^0-9a-zA-Z\-_\. ]/i', $this->uploaded_file['name'])) {
//                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:5'), 'error' => 'fileName_characters', 'type' => 'error');
//                return false;
//            }

            /** Если нет такой директории, то создаем */
            $this->ensureDirectoryExists($this->realPath);

            if ($this->exists($this->realPath . $this->uploaded_file['name'])) {
                /** Файл с данным именем уже существует на сервере */
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:16'), 'error' => 'duplicate_file', 'type' => 'error');
                return false;
            }
        } else {
            /** Файл не удалось загрузить, загружаемый файл превышает параметр «UPLOAD_MAX_FILESIZE» или «POST_MAX_SIZE», проверьте настройки сервера! */
            $this->response = array('status' => 500, 'message' => lang('fileManager:errors:27'), 'error' => 'no_file_was_uploaded', 'type' => 'error');
            return false;
        }

        return true;
    }

    /**
     * Загрузка файла
     * @return bool
     */
    public function upload(): bool
    {
        if (!preg_match($this->options['accept_file_types'], $this->uploaded_file['name'])) {
            try {
                $this->file->move($this->realPath, $this->uploaded_file['name']);
                $uploadedId = $this->generator->saveFile($this->uploaded_file);
            } catch (FileException $e) {
                print_r($e->getMessage());
            }
        } else {
            $uploadedId = $this->generator->init($this->file)->save();
        }

        if (!$uploadedId) {
            /** Невозможно загрузить файл на сервер. */
            $this->response = array('status' => 500, 'message' => I18N::locale("Невозможно загрузить файл на сервер", "Rasm qo'shildi!", "Image added!"), 'error' => 'unable_upload_file', 'type' => 'error');
//            $this->response = array('status' => 500, 'message' => lang('fileManager:errors:17'), 'error' => 'unable_upload_file', 'type' => 'error');
            return false;
        }

        /** Файл загружен. */
        $link = $this->getFileUrl($uploadedId);
        $this->response = array('status' => 200, 'message' => I18N::locale("Файл загружен.", "Rasm qo'shildi!", "Image added!"), 'error' => '', 'fileId' => $uploadedId, 'link' => $link, 'type' => 'success');
//        $this->response = array('status' => 200, 'message' => lang('fileManager:messages:1'), 'error' => '', 'fileId' => $uploadedId, 'link' => $link, 'type' => 'success');

        return true;
    }

    /**
     * Метод для получения картинки при загрузках в контент (tinymce)
     *
     * @param $fileId
     * @return string|null
     */
    public function getFileUrl($fileId): ?string
    {
        if ($file = connect('files')->where('fileId', $fileId)->get('array')) {
            $image = ImageSizes::init()->get(null, $file);
            return $image['original'] ?? '';
        }

        return null;
    }

    /**
     * Валидация изображения
     * @return bool
     */
    protected function is_valid_image_file(): bool
    {
        if (!preg_match('/\.(gif|jpe?g|png|webp)$/i', $this->file->getClientOriginalName())) {
            return false;
        }

        return !!$this->image_type();
    }

    protected function image_type()
    {
        $fp = fopen($this->file, 'r');
        $data = fread($fp, 4);
        fclose($fp);
        // GIF: 47 49 46 38
        if ($data === 'GIF8') {
            return self::IMAGETYPE_GIF;
        }
        // JPG: FF D8 FF
        if (bin2hex(substr($data, 0, 3)) === 'ffd8ff') {
            return self::IMAGETYPE_JPEG;
        }
        // PNG: 89 50 4E 47
        if (bin2hex(@$data[0]).substr($data, 1, 4) === '89PNG') {
            return self::IMAGETYPE_PNG;
        }
        if ($data === 'RIFF') {
            return self::IMAGETYPE_WEBP;
        }
        return false;
    }

    protected function get_image_size()
    {
        if ($this->options['image_library']) {

            if (extension_loaded('imagick')) {
                $image = new \Imagick();
                try {
                    if (@$image->pingImage($this->file)) {
                        $dimensions = array($image->getImageWidth(), $image->getImageHeight());
                        $image->destroy();

                        return $dimensions;
                    }

                    return false;

                } catch (\Exception $e) {
                    error_log($e->getMessage());
                }
            }

            if ($this->options['image_library'] === 2) {
                $cmd = $this->options['identify_bin'];
                $cmd .= ' -ping '.escapeshellarg($this->file);
                exec($cmd, $output, $error);
                if (!$error && !empty($output)) {
                    // image.jpg JPEG 1920x1080 1920x1080+0+0 8-bit sRGB 465KB 0.000u 0:00.000
                    $infos = preg_split('/\s+/', substr($output[0], strlen($this->file)));
                    return preg_split('/x/', $infos[2]);
                }

                return false;
            }
        }

        if (!function_exists('getimagesize')) {
            error_log('Function not found: getimagesize');

            return false;
        }

        return @getimagesize($this->file);
    }

    public function get_config_bytes($val): float
    {
        $val = trim($val);
        $last = strtolower($val[strlen($val)-1]);
        $val = (int)$val;
        switch ($last) {
            case 'g':
                $val *= 1024;
            case 'm':
                $val *= 1024;
            case 'k':
                $val *= 1024;
        }
        return $this->fix_integer_overflow($val);
    }

    // Исправлено переполнение 32-разрядных целых чисел со знаком,
    // работает для размеров до 2 ^ 32-1 байт (4 гигабайта - 1):
    protected function fix_integer_overflow($size): float
    {
        if ($size < 0) {
            $size += 2.0 * (PHP_INT_MAX + 1);
        }

        return $size;
    }

    /**
     * @param $id
     * @return false|mixed
     */
    protected function get_server_var($id)
    {
        if (isset($_SERVER[$id])) {
            return @$_SERVER[$id];
        }

        return false;
    }


}