<?php

namespace Clevercherry\Backslash4\Models;

use Clevercherry\Backslash4\Backslash4;
use Illuminate\Support\Facades\Storage;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Illuminate\Support\Carbon;

class MediaManager extends Model
{
    use scopeActive;
    //protected $table = 'media_manager';
    public $drive = 'local';

    function __construct()
    {
        $drive = env('MEDIASYSTEM_DISK', env('FILESYSTEM_DISK','local'));
    }

    /**
     * Override the generic method, as the initial page loads needs
     * links to the ajax POST method
     */
    public static function adminListing($pageSize = 10): LengthAwarePaginator
    {
        $listing = parent::adminListing($pageSize);
        if(Str::endsWith($listing->path(),'/media'))
        {
            //initial page but nees to load ajax style links
            $listing->withPath(route('admin.mediamanager.index.ajax'));
        }

        return $listing;
    }

    /**
     * whether this item allows editing on the admin index page
     */
    public function allowEdit(): bool
    {
        return false; //there is no edit function as such in media manager at this time
    }

    public static function getMediaStorageDrive()
    {
        return env('MEDIASYSTEM_DISK', env('FILESYSTEM_DISK','local'));
    }

    public static function getStoragePath($path='')
    {
        if (self::getMediaStorageDrive()=='local') {
            return storage_path("app/public/{$path}");
        }
        //else

        if (Str::startsWith($path, 's3:')) {
            $path = substr($path,3);
        }

        $tenant_prefix = empty(config('backslash4.tenants-on',0)) ? '' : '/'.Backslash4::getTenantKey();

        return env('APP_NAME','bs4').$tenant_prefix."/{$path}";
    }

    /**
     * Returns if videos have been enabled in media manager in the dashboard config file
     * 'mediamanger' =>[ 'videos_enabled' => true ],
     * @return bool     whether this setting has been enabled in the dashboard config file 
     *                  (for main backslash4 or if in tenancy mode in the tenancy dashboard file)
     */
    public static function isVideosEnabled() : bool
    {
        // double not !! tests if object is considered true or false from other types
        return  !!Backslash4::getDashboardConfigSetting('MediaManger.videos_enabled',false);
    }

    /**
     * Return the deafult video types for upload, which can be overridden in the Dashboard config file
     * 
     *   Video Type	Extension	MIME Type
     *   ---------- ---------   ------------------
     *   Flash      .flv        video/x-flv
     * > MPEG-4     .mp4    	video/mp4
     * > MPEG-4     .m4v    	video/x-m4v
     *   iPhone Index	.m3u8	    application/x-mpegURL
     *   iPhone Segment	.ts	    video/MP2T
     *   3GP Mobile	.3gp	    video/3gpp
     * > QuickTime	.mov	    video/quicktime
     *   A/V Interleave	.avi	video/x-msvideo
     *   Windows Media	.wmv	video/x-ms-wmv
     * > Web Movie      .webm   video/webm
     * @return array[string]     two string [0] - human readable list of file types, [1] mime and filename types
     */
    public static function getVideoTypes(): array
    {
        $default = [ 'MOV MP4 M4V WEBM',
                     'video/quicktime video/x-msvideo video/mp4 video/x-m4v video/webm .mov .mp4 .m4v .webm'
                   ];
        return (array) Backslash4::getDashboardConfigSetting('MediaManger.video_types', $default);
    }

    public static function getImageTypes(): array
    {
        $default = [ 'WEBP, PNG, JPG, JPEG', 
                     'image/png, image/jpeg, image/webp, .png, .jpeg, .jpg, .webp'];
        return (array) Backslash4::getDashboardConfigSetting('MediaManger.image_types', $default);
    }

    public static function getFileRequirements() : string
    {
        return str_replace(', JPEG','',self::getImageTypes()[0]); //'JPG, PNG, WEBP';
    }

    public static function getFileAccept() : string
    {
        return self::getImageTypes()[1]; //'image/png, image/jpeg, image/webp, .png, .jpeg, .jpg, .webp';
    }

    /**
     * @return String   the best path to the image on the server or S3
     */
    public function bestPath()
    {
        if (!empty($this->optimised)) {
            return trim($this->optimised,'\\/ \t\n\r');
        }
        //else

        if (!empty($this->original)) {
            //because original is a keyword
            return trim($this->getRawOriginal('original'),'\\/ \t\n\r');
        }
        //else

        return trim($this->thumbnail ?? '', '\\/ \t\n\r');
    }

    /**
     * @return String   the best thumbnail path to the image on the server or S3
     */
    public function bestThumbnailPath()
    {
        if (!empty($this->thumbnail)) {
            return trim($this->thumbnail,'\\/ \t\n\r');
        }
        //else

        if (!empty($this->optimised)) {
            return trim($this->optimised,'\\/ \t\n\r');
        }
        //else

        //original is also a laravel property
        return trim($this->getRawOriginal('original') ?? '','\\/ \t\n\r');
    }

    /**
     * @return String   the full url to the image on the server or S3
     */
    public function url($fallback=null) : String
    {
        return $this->optimisedUrl($fallback);
    }

    /**
     * returna  proper url regardless of the path provioded local, s3 or http
     * @parm String   file path
     * @return String     fully qualified url
     */
    public static function getPathToUrl($path) : String
    {
        if (Str::startsWith($path,'s3:')) {
            return Storage::drive('s3')->url(self::getStoragePath($path));
        }
        if (Str::startsWith($path,['http:','https:'])) {
            return $path;
        }
        //else
        
        return Storage::url($path);
    }

    /**
     * Override the default function that genarets paths from CMS backslash4 condfig slugs
     * @return String   the full url to the image on the server or S3
     */
    public function getUrl($fallback=null) : String
    {
        return $this->optimisedUrl($fallback);
    }

    public static function urlById($id)
    {
        $media = self::where('id', $id)->first();

        return $media->optimisedUrl();
    }
    public function getFields()
    {
        if(empty($this->fields))
            return [];    

        return Backslash4::jsonDecodeWithDefault($this->fields, true, []);
    }
    public function getFieldsSetting($index)
    {
        $fields = $this->getFields();
        if(isset($fields[$index]))
            return $fields[$index];
        //else

        return null;
    }

    public function sizedUrl($size='opt') : string
    {
        $path = trim($this->optimised,'\\/ \t\n\r');
        if($size=='opt')
            return $this->optimisedUrl();
    
        $extras = $this->getFieldsSetting('extrafiles');
        if(!empty($extras))
        {
            $ext = '.'.$size.'.webp';
            foreach($extras as $extra)
            {
                if(Str::endsWith($extra, $ext))
                {
                    $path = dirname($path).'/'.$extra;

                    if(Str::startsWith($path,'s3:'))
                    {
                        return Storage::drive('s3')->url(self::getStoragePath($path));
                    } 
                    if(Str::startsWith($path,['http:','https:']))
                    {
                        return $path;
                    }
                    //else
                    if( Storage::fileExists(self::getStoragePath($path)) )
                    {
                        return Storage::url($path);

                    }
                }
            }

        }

        return $this->optimisedUrl();
    }

    public function optimisedUrl($fallback=null) : String
    {
        $path = $this->bestPath();
        if(empty($path))
            return $fallback ?? '';
        
        if(Str::startsWith($path,'s3:'))
            return Storage::drive('s3')->url(self::getStoragePath($path));
        if(Str::startsWith($path,['http:','https:']))
            return $path;

        //else
        
        return Storage::url($path);
    }

    public function originalUrl($fallback=null)
    {
        $path = trim($this->getRawOriginal('original'),'\\/ \t\n\r'); //because original is a keyword
        if(empty($path))
            return $fallback ?? '';

        if(Str::startsWith($path,'s3:'))
            return Storage::drive('s3')->url(self::getStoragePath($path));
        if(Str::startsWith($path,['http:','https:']))
            return $path;

        //else

        return Storage::url($path);
    }

    public function thumbnailUrl($fallabck=null)
    {
        $path = $this->bestThumbnailPath();
        if(empty($path))
            return $fallback ?? '';

        if(Str::startsWith($path,'s3:'))
            return Storage::drive('s3')->url(self::getStoragePath($path));
        if(Str::startsWith($path,['http:','https:']))
            return $path;

        //else

        return Storage::url($path);
    }

    public function getBaseFolder()
    {
        return basename(pathinfo(trim($this->getRawOriginal('original'),'\\/ \t\n\r'), PATHINFO_DIRNAME));
    }

    /**
     * Retrieves the "alt" field value.
     *
     * This is typically used for alternative text for images or media
     * to improve accessibility and SEO.
     *
     * @return string The alt text value, or an empty string if the field is missing.
     */
    public function getAlt()
    {
        return $this->getField('alt','');
    }

    public function getBestDims()
    {
        if(($this->opt_width==null) || ($this->opt_height==null))
        {
            $path = $this->bestPath();
            if(Str::startsWith($path,'s3:'))
            {
                // get image size from remote S3 image -- we may have to disable this if it slows it down too much
                $contents = Storage::drive('s3')->get(self::getStoragePath($path));
                if($contents!=null)
                {   $im = imagecreatefromstring($contents);
                    $this->opt_width = imagesx($im) ?? null;
                    $this->opt_height = imagesy($im) ?? null;
                    $this->save();
                }
            } else //local file system
            {
                $fs = new Filesystem;
                $path = storage_path('app/public/' . $path);
                if($fs->exists($path))
                {   $dims = getimagesize($path);
                    // {"0":566,"1":400,"2":18,"3":"width=\"566\" height=\"400\"","bits":8,"mime":"image\/webp"}
                    $this->opt_width = $dims[0];
                    $this->opt_height = $dims[1];
                    $this->save();
                }
            }
        }
        //else
        return [$this->opt_width, $this->opt_height];
    }

    public function getField($field, $default=null)
    {
        if (empty($this->fields)) {
            return $default;
        }
        //else

        $json = Backslash4::jsonDecodeWithDefault($this->fields, true, []);
        if (empty($json)) {
            return $default;
        }

        if (isset($json[$field])) {
            return $json[$field];
        }

        //else
        return $default;
    }

    //static versions all start with get

    public static function defaultImage()
    {
        return self::first();
    }

    public static function getMediaItem($id)
    {
        $singleid = explode(',',$id,1);
        $media = null;

        if (!empty($singleid) && $singleid > 0) {
            $media = self::where('id', $singleid[0] )->first();
        }

        if ($media == null) {
            return new MediaManager;
        }

        return $media;
    }

    /**
     * @param String $json
     */
    public static function getMediaItemsFromJson($json)
    {
        $medias = [];
        $obj = Backslash4::jsonDecodeWithDefault($json, true, []);
        if (isset($obj['id'])) {
            //single object
            $obj = [];
            $obj[] = Backslash4::jsonDecodeWithDefault($json, true, []);
        }
        if (!empty($obj)) {
            foreach ($obj as $mediaItem) {
                $m = self::where('id', $mediaItem['id'])->first();
                if (!empty($m)) {
                    foreach ($mediaItem as $key => $val) {
                        if($key!='id') {
                            $m->$key = $val;
                        }
                    }
                    $medias[] = $m;
                }
            }
        }

        return $medias;
    }

    /**
     * @param String $json
     */
    public static function getFirstMediaItemFromJson($json)
    {
        $media = null;
        $obj = Backslash4::jsonDecodeWithDefault($json, true, []);
        
        if(isset($obj['id']))
        {
            //single object
            if ($obj['id'] > 0) {
                $obj = [];
                $obj[] = Backslash4::jsonDecodeWithDefault($json, true, null);
            } else {
                $obj = null;
            }

        }
        
        if (!empty($obj)) {
            $m = self::where('id', $obj[0]['id'])->first();
            if (!empty($m)) {
                foreach ($obj[0] as $key => $val) {
                    if ($key!='id') {
                        $m->$key = $val;
                    }
                }
                $media = $m;
            }
        }
        
        if ($media == null) {  
            return new MediaManager;
        }
        
        return $media;
    }

    public static function getUploadDirectories(
                        $default='Select a folder for your Uploads',
                        $create='Create new folder',
                        $basepath='images')
    {
        $dirs = [];
        if (!empty($default)) {
            $dirs [''] = $default;
        }
        if (!empty($create)) {
            $dirs ['+'] = $create;
        }

        $drive = self::getMediaStorageDrive();
        $storagePath = self::getStoragePath($basepath);

        $ldirs=[];
        if ($drive=="s3") {
            if(Storage::disk($drive)->exists($storagePath) ) {
                $ldirs = Storage::drive($drive)->directories($storagePath);
            }
        } else if (File::exists(storage_path('app/public/'.$basepath))) {
            $fs = new Filesystem;
            $ldirs = $fs->directories($storagePath);
        }

        foreach ($ldirs as $d) {
            $dirs[basename($d)] = basename($d);
        }

        //check for other dirs in mediamanager items
        if ($drive=="s3") {
            $others = self::where('original', 'not like', 's3:%');
            foreach($dirs as $d)
                $others->where('original', 'not like', "{$basepath}/{$d}%");
        } else {
            $others = self::where('original', 'like', 's3:%');
            foreach ($dirs as $d) {
                $others->where('original', 'not like', "s3:{$basepath}/{$d}%");
            }
        }

        foreach ($others->get() as $d) {
            $folder = $d->getBaseFolder();
            if (!isset($dirs[$folder] )) {
                $dirs[$folder] = $folder;
            }
        }

        return $dirs;
    }

    /**
     * return the media_manager models in database that match the directory
     */
    public static function getMediaInDirectory($dir='')
    {
        $storagePath = 's3:'.$dir;

        $mmedia = self::where('optimised', 'like', $dir.'/%')
                    ->orWhere('original', 'like', $dir.'/%')
                    ->orWhere('optimised', 'like', $storagePath.'/%')
                    ->orWhere('original', 'like', $storagePath.'/%');

        return $mmedia->active()->get();
    }

    /**
     * return the files in the filesystem that match the directory
     */
    public static function getFilesInDirectory($dir='')
    {
        $mediaStorage = self::getMediaStorageDrive();
        $files=[];
        if ($mediaStorage=="local")
        {
            //return Storage::files(self::getStoragePath($dir));
            $cacheKey = 'media_manager_files_local_' . md5(self::getStoragePath($dir));
            $time = config('backslash4.cache.files.local.expire', 10800); // 3 hours default
            $files = cache()->remember($cacheKey, $time, function() use ($dir) {
                return File::files(self::getStoragePath($dir));
            });
        } else if ($mediaStorage=="s3")
        {
            // Cache the files for 3 hours to reduce S3 calls
            $cacheKey = 'media_manager_files_s3_' . md5(self::getStoragePath($dir));
            $time = config('backslash4.cache.files.s3.expire', 10800); // 3 hours default
            $files = cache()->remember($cacheKey, $time, function() use ($dir) {
                return Storage::disk('s3')->files(self::getStoragePath($dir));
            });
        }
        //else unknown file system - you should implement this here or in your app

        $fileinfos=[];
        foreach($files as $file)
        {
            if(method_exists($file, 'isFile') && $file->isFile())
            {
                if(self::getMediaStorageDrive()=="s3")
                    $url = Storage::drive('s3')->url(self::getStoragePath($dir.'/'.$file->getFilename()));
                else
                    $url=Storage::url($dir.'/'.$file->getFilename());

                $fileinfos[] = ['filename' => $file->getFilename(),
                                'extension' => $file->getExtension(),
                                //'path' => $file->getPath(),
                                //'aTime' => $file->getATime(),
                                'cTime' => (new Carbon($file->getCTime()))
                                                ->timezone('Europe/London')
                                                ->toDateTimeString(),
                                //'mTime' => $file->getMTime(),
                                //'perms' => $file->getPerms(),
                                'size' => $file->getSize(),
                                'url' => $url
                        ];
            } else if(!method_exists($file, 'isFile'))
            {

                $fileinfos[] = ['filename' => pathinfo($file, PATHINFO_BASENAME),
                                'extension' => pathinfo($file, PATHINFO_EXTENSION),
                                //'path' => $file->getPath(),
                                //'aTime' => $file->getATime(),
                                'cTime' => (new Carbon(Storage::drive('s3')->lastModified($file)))
                                                ->timezone('Europe/London')
                                                ->toDateTimeString(),
                                //'mTime' => $file->getMTime(),
                                //'perms' => $file->getPerms(),
                                'size' => Storage::drive('s3')->size($file),
                                'url' => ($mediaStorage=="s3") ?  Storage::drive('s3')->url($file) : $file
                        ];
            }
        }
        return $fileinfos;
    }

    /**
     * Override bs4\Model as media items do not have blocks
     */
    public function hasBlocks()
    {
        return false;
    }

    /**
     * return a list of fields that should be added to the top of the admin index pages
     * @return Array | null           list of query fieldnames as key, with friendly string names
     *                                [$field_name]="friendly string"
     *                                that will be passd as query parameters or null or empty array
     *                                the fieldnames do not have to reflect the fieldnames in the table, they can be shortened versions that will be passed as request parameters
     *                                as you will also be overriding the scopeIndexSearch function below using the Request() object to get the extra parameters
     */
    public static function getAdminSearchDropDowns() : Array | null
    {
        return array( 'dir' => "Directory");
    }

    /**
     * return the dropdown list for the above search dropdown key
     * @param  String $field_name        the query fieldname posted when a query is submitted
     * @return Array | null              the list of valid options [value]=String where value is passed in the query, the first option should have a key of the empty string and will be selected by default
     */
    public static function getAdminSearchDropDownList($field_name) : Array | null
    {
        if ($field_name=='dir') {
            return self::getUploadDirectories('All folders','');
        }
        
        return null;
    }

    /**
     * Override the search function
     */
    public function scopeIndexSearch($query, $q)
    {   
        $dir = Request()->dir;
        
        if (!empty($q)) {
            $q = "%{$q}%";
            $query->orWhere('original', 'like', $q);
        }

        if (!empty($dir)) {
            $query->where( function ($query) use($dir) {

                $dir = 'images/'.$dir;
                $storagePath = 's3:'.$dir;

                $query->where('optimised', 'like', $dir.'/%')
                    ->orWhere('original', 'like', $dir.'/%')
                    ->orWhere('optimised', 'like', $storagePath.'/%')
                    ->orWhere('original', 'like', $storagePath.'/%');
            });
        }

        return $query;
    }

    public static function getFrontEndRoute($default, $is_index=false) : String | null
    {
        return null;
    }

    /**
     * Whether the overwrite swicth is visible, default is setting in backslash4 config
     * but could be overridden in the app, either tennat or other security
     */
    public static function overwriteSwitch() : bool
    {
        // not empty to enforce boolean
        return !empty(config('backslash4.media.overwrite-switch', 0));
    }

    /**
     * the type of the embedded video is we can tell what it is
     * @param   string $url     the code or url or embedding html for an embedded video
     * @return  string          empty string is we can't determine the type from the url
     */
    public static function getVideotypeFromFullUrl($url) : String
    {
        $url = strtolower($url);
        if (str_contains($url,'wistia')) {
            //z2aivxn1ku - 10 this might change
            return 'wistia';
        }
        if (str_contains($url,'vimeo')) {
            //421439852 - 9
            return 'vimeo';
        }

        if (str_contains($url,'youtu')) {
            //UlUKFiamTws - 11 length is not a differentiator
            return 'youtube';
        }

        if (str_contains($url,'matterport')) {
            //L7dq6XNRZ4T - 11
            return 'matterport';
        }
        //else unknown

        return '';
    }


    /**
     * Strip out any html to get only the video reference code for any video embedding
     * @param   string $url     the code or url or embedding html for an embedded video
     * @return  string          just the video refernce number/code needed to embed a video
     */
    public static function stripVideoUrl($url) : string
    {   
        $em = trim($url);
        // strip youtube and vimeo  down to the actual video reference/id code
        $p = strpos($em,'http://');
        if ($p>0) {
            $em = substr($em, $p);
        }

        $p = strpos($em,'https://');
        if ($p>0){
            $em = substr($em, $p);
        }
        
        $p = strpos($em,'.wistia.com/');        
        if ($p>0) {
            // https://ellismachinery.wistia.com/medias/z2aivxn1ku
            $em = substr($em, $p);
        }

        $em = str_replace('.wistia.com/medias/', '' , $em);
        $em = str_replace('https://fast.wistia.net/embed/iframe/', '', $em);
        $em = str_replace('https://fast.wistia.com/embed/medias/' , '', $em);

		//https://my.matterport.com/show/?m=L7dq6XNRZ4T
        $em = str_replace('https://my.matterport.com/show/?m=' , '', $em);
        
        //https://www.youtube.com/watch?v=UlUKFiamTws
        $em = str_replace('<iframe width="560" height="315" src="https://www.youtube.com/embed/', '', $em);
        $em = str_replace('https://www.youtube.com/watch?v=', '' , $em);
        $em = str_replace('http://youtu.be/', '' , $em);
        $em = str_replace('https://youtu.be/', '' , $em);
        $em = str_replace('https://www.youtube.com/embed/', '' , $em);

        //https://vimeo.com/421439852
        $em = str_replace('<iframe src="https://player.vimeo.com/video/', '' , $em);
        $em = str_replace('https://player.vimeo.com/video/', '' , $em);
        $em = str_replace('https://vimeo.com/', '' , $em);
        
        $p=strpos($em,'.jsonp');
        if ($p>0) {
            $em = substr($em, 0, $p);
        }
        $p=strpos($em,'?');
        if ($p>0) {
            $em = substr($em, 0, $p);
        }
        $p=strpos($em,'&');
        if ($p>0) {
            $em = substr($em, 0, $p);
        }
        $p=strpos($em,'/');
        if($p>0) {
            $em = substr($em, 0, $p);
        }

        $p=strpos($em,'"');
        if($p>0) {
            $em = substr($em, 0, $p);
        }
    
        return $em;
    }

}
