<?php

namespace Hura8\System\Controller;

use Hura8\Interfaces\AppResponse;
use Hura8\Interfaces\FileHandleInfo;
use Hura8\Interfaces\FileHandleResponse;
use Hura8\System\FileSystem;
use Hura8\System\HuraImage;
use Hura8\System\IDGenerator;
use League\MimeTypeDetection\FinfoMimeTypeDetector;

/**
 * @date 16-Jan-2024
 * @description based-class to handling files, this is used for :
 * - FileUpload class
 * - CopyFileFromUrl class
 * ...
 *
 */

abstract class bFileHandle
{

    static $image_extensions = array(".jpg",".jpeg",".gif", ".png", ".webp", '.avif', ".ico");

    protected $permit_file_extensions = array(
        ".jpg",".jpeg",".gif", ".png", ".webp", '.avif',
        ".doc", ".docx",".xls",".xlsx", ".ppt", ".pdf",
        ".rar", ".zip",
        //".avi",".mov",".mpg", ".wmv", ".mpeg",
        //".mp3",".mp4", ".ogg", ".oga", ".wav", ".wma"
    );

    protected $permit_mine_types = array(
        'image/jpeg', 'image/png','image/gif', 'image/webp', 'image/avif',

        // zip files
        'application/zip', 'application/x-zip-compressed', 'multipart/x-zip', 'application/x-compressed',
    );

    // full system's path where the file will be finally stored or it's currently there for processing
    // example: /var/www/html/domain.com/public_html/media/product/
    protected  $target_dir = "";

    // directory appears to the public
    // example: /media/product/
    protected  $public_dir = "";

    protected  $tmp_dir = ROOT_DIR . "/var/tmp_upload/"; // system tmp dir to store uploaded file for security examination before moving to target_dir
    protected  $tmp_folder = ""; // tmp folder per session in the $tmp_dir to hold user's files. This folder will be removed when operation is complete

    protected  $setup_success = false;

    /**
     * @param string $target_dir will be created if not exist i.e. media/product/ or media/user_upload/date('d-m-Y')
     * @param ?array $permit_file_extensions null to allow all default file extensions
     */
    public function __construct( string $target_dir, ?array $permit_file_extensions = null ) {

        if(is_array($permit_file_extensions)) {
            $this->permit_file_extensions = $permit_file_extensions;
        }

        if($target_dir) {
            $this->target_dir = PUBLIC_DIR . DIRECTORY_SEPARATOR. $target_dir;
            $this->public_dir = "/".$target_dir;
        }

        $setup_res = $this->setUp();
        if($setup_res->getStatus() == AppResponse::SUCCESS) {
            $this->setup_success = true;
        }
    }


    /**
     * @description (rename if set) and resize file
     * @param string $name
     * @param string $new_name
     * @param array $resized_sizes [] array('small' => ['width' => 100, 'height' => 100], 'large' => ['width' => 200, 'height' => 200] )
     * @return array|false
     */
    public function resizeFile(string $name, string $new_name = '', array $resized_sizes = []) {

        if($new_name) {
            $renamed = $this->renameFile($name, $new_name);
            if(!$renamed) {
                return false;
            }

            //$file_name   = $renamed['file_name'];
            //$public_path = $renamed['public_path'];
            $local_path  = $renamed['local_path'];
        }else{
            //$file_name   = $name;
            //$public_path = $this->public_dir . "/".$name;
            $local_path  = $this->target_dir . "/" . $name;
        }

        $objHuraImage = new HuraImage();

        list(, $expected_files, ) =  $objHuraImage->resize($local_path, $resized_sizes);

        return $expected_files;
    }


    /**
     * @description we can rename the uploaded file to new file name, for example: we want product's image have the format [PRODUCT_ID]-name
     * @param string $name
     * @param string $new_name
     * @return array | false
     */
    public function renameFile(string $name, string $new_name) {
        if(@rename($this->target_dir . "/" . $name, $this->target_dir . "/" . $new_name)){
            return [
                "file_name"   => $new_name,
                "public_path" => $this->public_dir . "/".$new_name,
                "local_path"  => $this->target_dir . "/" . $new_name,
            ];
        }

        return false;
    }


    // public utility
    public static function getFileExtension($file_name) {
        return strtolower(strrchr($file_name,"."));
    }


    /**
     * @description run clean up after finish using the FileHandle instance
     */
    public function cleanUp() {
        if($this->tmp_folder) {
            FileSystem::removeDir($this->tmp_folder);
        }
    }

    protected function processFile(
        $original_file_name,
        $original_file_tmp_name, // temporary uploaded file as in case of $_FILES[$input_file_name]["tmp_name"]
        $fixed_file_name="",
        $max_file_size=0
    ) : FileHandleResponse {

        if(!$original_file_name) {
            return new FileHandleResponse(AppResponse::ERROR, 'no file', null);
        }

        $file_size = filesize($original_file_tmp_name);
        if($max_file_size > 0 && $max_file_size < $file_size) {
            return new FileHandleResponse(AppResponse::ERROR, 'Size is too large: '.round($file_size/1000).'KB', null);
        }

        //validate extension
        $file_ext   = self::getFileExtension($original_file_name);

        if(!in_array($file_ext, $this->permit_file_extensions)) {
            return new FileHandleResponse(AppResponse::ERROR, "Type ".$file_ext." is not allowed!", null);
        }

        $file_name = substr($original_file_name, 0, strrpos($original_file_name,"."));
        $file_name = preg_replace("/[^a-z0-9_-]/i","", $file_name);
        $file_name = substr($file_name, 0, 50); // max- length
        $clean_file_name = ($fixed_file_name) ?:  $file_name . $file_ext;

        $tmp_file_path = $this->tmp_folder . "/". $clean_file_name;
        $return_data = null;

        //debug_var([$original_data, $tmp_file_path]);
        if(@rename($original_file_tmp_name, $tmp_file_path)){

            $is_file_image = (in_array($file_ext, static::$image_extensions ));

            if($is_file_image) {
                list($width, $height) = getimagesize($tmp_file_path);
            }else{
                $width = 0;
                $height = 0;
            }

            $detector = new FinfoMimeTypeDetector();
            $mimeType = $detector->detectMimeTypeFromPath($tmp_file_path);

            // if image, we re-create and optimize it
            if($is_file_image) {
                if(in_array($mimeType, $this->permit_mine_types)) {
                    $objHuraImage = new HuraImage();
                    if($objHuraImage->create($tmp_file_path, $this->target_dir . DIRECTORY_SEPARATOR . $clean_file_name)){
                        $return_data = new FileHandleInfo([
                            "file_name" => $clean_file_name,
                            "public_path" => $this->public_dir . "/".$clean_file_name,
                            "local_path" => $this->target_dir . "/" . $clean_file_name,
                            "mime_type" => $mimeType,
                            "file_size" => $file_size,
                            "file_ext" => $file_ext,
                            "width" => $width,
                            "height" => $height,
                        ]);
                    }
                }

            }elseif(@rename ($tmp_file_path, $this->target_dir . DIRECTORY_SEPARATOR . $clean_file_name )) {
                $return_data = new FileHandleInfo([
                    "file_name" => $clean_file_name,
                    "public_path" => $this->public_dir . "/".$clean_file_name,
                    "local_path" => $this->target_dir . "/" . $clean_file_name,
                    "mime_type" => $mimeType,
                    "file_size" => $file_size,
                    "file_ext" => $file_ext,
                    "width" => 0,
                    "height" => 0,
                ]);
            }
        }

        // delete tmp file on server
        if(file_exists($original_file_tmp_name)) {
            @unlink($original_file_tmp_name);
        }

        if($return_data) {
            return new FileHandleResponse(AppResponse::SUCCESS, 'Success', $return_data);
        }

        return new FileHandleResponse(AppResponse::ERROR, 'Unknown', null);
    }


    protected function setUp(): AppResponse
    {
        // check target dir
        if($this->target_dir && !is_dir($this->target_dir)) {
            @mkdir($this->target_dir, 0755, true);
        }

        if(!file_exists($this->target_dir)) {
            return new AppResponse(AppResponse::ERROR, $this->target_dir.' not exists');
        }

        // create tmp_folder to upload file to
        $this->tmp_folder = $this->create_tmp_folder();
        if(!$this->tmp_folder) {
            return new AppResponse(AppResponse::ERROR, "Check ".$this->tmp_dir." and make sure it exists and writable");
        }

        return new AppResponse(AppResponse::SUCCESS);
    }


    protected function create_tmp_folder() : ?string {
        $tmp_folder = $this->tmp_dir . IDGenerator::createStringId(5);
        if(!@mkdir($tmp_folder, 0777, true)) {
            return null;
        }

        // retest
        if(!$tmp_folder || !is_dir($tmp_folder)) {
            return null;
        }

        return $tmp_folder;
    }


}
