<?php

namespace App\Models;

use App\Events\AttachmentSoldToClientEvent;
use App\Events\NewPictureReleasedEvent;
use App\Events\NewFileReleasedEvent;
use App\Http\Resources\PictureOrVideoResource;
use App\Jobs\SendMailsToFansWhenModelPostsContent;
use App\Traits\SimpleErrorsHandling;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;
use Lakshmaji\Thumbnail\Facade\Thumbnail;

/**
 * Class Attachment
 *
 * @property User $model
 * @property mixed created_at
 *
 * @package App
 */
class Attachment extends Model
{
    use SimpleErrorsHandling;

    /** Directories for files */
    const DIR_NAME_ATTACHMENT_THUMBS = 'public/attachment_thumbs';
    const DIR_NAME_ATTACHMENT_FILES = 'model_attachments';    // storage/app/model_attachments/

    /** Max size of the Model's uploaded */
    const MAX_FILE_SIZE_IN_BYTES = 1073741824; // 1 GB, acc. to clients request 8.01.2020

    /** Default attachment thumb for the case on some reasons thumb from attachment will not be generated */
    const DEFAULT_THUMB_FILE_NAME = 'default_attachment_thumb.svg';

    /** @var array $attributes */
    protected $attributes = [
        'file_name'     => '',
        'thumb_file'    => 'public/attachment_thumbs/'.self::DEFAULT_THUMB_FILE_NAME,
        'title'   => null,
        'description'   => null,
        'price'         => null,
        'tags'          => null,
        'visible_for_fans' => null,

    ];

    /**
     * @var array
     */
    protected $fillable = [
        'user_id', 'title', 'description', 'price', 'tags', 'video_duration', 'price', 'visible_for_fans'
    ];

    /**
     * @var array
     */
    protected $guarded = [
        'file_name', 'mime_type',
    ];

    public static function boot(){
        parent::boot();
        static::creating(function (Attachment $attachment){

            $attachment->calculateDuration();
            $attachment->makeThumb();
        });
    }
    public function getTypeAttribute()
    {
        return explode('/', $this->mime_type)[0] === 'video' ? 'video' : 'picture';
    }

    /**
     * Relation to the Model
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function model()
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    /**
     * Relation to the Model's Video Buyings
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function attachmentBuyings()
    {
        return $this->hasMany( FanAttachmentBuying::class, 'attachment_id', 'id');
    }


    public static function storeFile(Request $request): Attachment
    {

        $newFile = new self();
        $newFile->fillFromRequiest($request);

        $model = $newFile->model;
        \Log::debug($model->login . ' published '. $newFile->type . ' ' . $newFile->title);

        if($newFile->type === 'video') {
            $fullPath = $newFile->getFilePath();
            $command = "ffmpeg -i " . $fullPath . " -movflags faststart -acodec copy -vcodec copy " . $fullPath;
            exec($command, $output);
        }

        if($newFile->price > 0) {
            event(new NewFileReleasedEvent(
                    ['twitterMessageEvent' => TwitterMessageText::EVENT_NEW_FILE_RELEASED], $newFile->model));

            SendMailsToFansWhenModelPostsContent::dispatch($newFile->model, __('backend.' . $newFile->type), $newFile->title);
        }

        return $newFile;
    }


    private function fillFromRequiest($request)
    {
        $user           = Auth::user() ?? User::find($request->modelId);
        $fileName       = 'model_' . $user->id . uniqid('', true);
        $file           = $request->file('file');
        $path = Storage::putFileAs(
            self::DIR_NAME_ATTACHMENT_FILES, $file, $fileName . '.' . $file->getClientOriginalExtension()
        );

        $this->file_name = $path;
        $this->user_id = $user->id;
        $this->title = $request->title;
        $this->description = $request->description;
        $this->price = $request->price;
        $this->tags = implode(', ', $request->input('tags', []));
        $this->visible_for_fans = $request->visible_for_fans;
        $this->mime_type =  $file->getClientMimeType();
        $this->type = explode('/', $file->getClientMimeType())[0] === 'video' ? 'video': 'picture';
        $this->is_approved = (int)$request->price !== 0;
        $this->save();

        return $this;
    }

    public function makeThumb()
    {
        if( ! Storage::disk('local')->exists(self::DIR_NAME_ATTACHMENT_THUMBS) ) {
            Storage::disk('local')->makeDirectory(self::DIR_NAME_ATTACHMENT_THUMBS);
        }

        if(!Storage::disk('local')->exists($this->file_name)) {
            $this->thumb_file = 'public/attachment_thumbs/'.self::DEFAULT_THUMB_FILE_NAME;
            return ;
        }

        if ($this->type === 'video') {
            $this->makeVideoThumb();
        } else {
            $this->makePictureThumb();
        }
    }

    /**
     * To make the video thumb and store it to video_thumbs in public directory
     * @comment need to change access mode for thumbs directory and for watermark (to 777)
     */
    protected function makeVideoThumb()
    {

        $ifFFmpegInstalled = trim(shell_exec('ffmpeg -version'));

        if ( $ifFFmpegInstalled !== '' && $this->file_name ) {

            $user = Auth::user() ?? $this->model;

            // file type is video
            // set storage path to store the file (image generated for a given video)
            $thumbnail_path   = storage_path().'/app/' . self::DIR_NAME_ATTACHMENT_THUMBS;

            $video_path       = storage_path().'/app/' . $this->file_name;

            $timestamp = now()->timestamp;

            // set thumbnail image name
            $thumbnail_image  = (Auth::guest() ? 'guest' : $user->id).'_'.$timestamp.".jpg";

            // set the thumbnail image "palyback" video button
            $water_mark       = storage_path().'/app/'. self::DIR_NAME_ATTACHMENT_FILES .'/watermark/p.png';

            // get video length and process it
            // assign the value to time_to_image (which will get screenshot of video at that specified seconds)
            // @my_todo: not realized in owners library  Lakshmaji\Thumbnail\
            //$time_to_image    = floor(($data['video_length'])/2);

            $time_to_image = 2; // seconds

            $thumbnail_status = Thumbnail::getThumbnail($video_path,$thumbnail_path,$thumbnail_image,$time_to_image);

            if( $thumbnail_status )
            {
                $this->thumb_file = self::DIR_NAME_ATTACHMENT_THUMBS .'/'. $thumbnail_image;
                //echo "Thumbnail generated";
                $thumbPath = storage_path() . '/app/' . $this->thumb_file;
                Image::make($thumbPath)
                    ->fit(config(' thumbnail.dimensions.width', 240), config(' thumbnail.dimensions.width', 320))
                    ->blur(50)
                    ->save($thumbPath);
            } else {
                //echo "thumbnail generation has failed";
            }
        }else{
            $this->thumb_file = self::DIR_NAME_ATTACHMENT_THUMBS .'/'.self::DEFAULT_THUMB_FILE_NAME;
        }
    }

    /**
     * To make the picture thumb and store it to picture_thumbs in public directory
     * @comment need to change access mode for thumbs directory and for watermark (to 777)
     *
     */
    public function makePictureThumb(): void
    {
        $user = Auth::user();

        // file type is picture
        // set storage path to store the file (image generated for a given picture)
        $thumbnail_path   = Storage::disk('local')->path(self::DIR_NAME_ATTACHMENT_THUMBS);

        $picture_path       = Storage::disk('local')->path($this->file_name);

        $timestamp = now()->timestamp;
        $extension = Image::make($picture_path)->extension;
        // set thumbnail image name
        $thumbnail_image  = (Auth::user()->id?? 'guest').'_'.$timestamp.'.'.$extension;

        // set the thumbnail image "palyback" picture button
        $water_mark       = storage_path().'/app/'. self::DIR_NAME_ATTACHMENT_FILES .'/watermark/p.png';
        Image::make($picture_path)
            ->fit(config(' thumbnail.dimensions.width', 240), config(' thumbnail.dimensions.width', 320))
            ->blur(50)
            ->save("$thumbnail_path/$thumbnail_image");
        $this->thumb_file = self::DIR_NAME_ATTACHMENT_THUMBS .'/'. $thumbnail_image;
    }

    /**
     * Store the MIME type of attachment file
     * @param $mimeType
     */
    public function setMimeType( $mimeType )
    {
        $this->mime_type = $mimeType;
    }

    /**
     * Returns the MIME type of file
     * @return string
     */
    public function getMimeType()
    {
        if ( is_null($this->mime_type) && !is_null($this->file_name) ) {

            $this->mime_type = Storage::disk('local')->mimeType($this->file_name);
            $this->save();
        }

        return $this->mime_type;
    }

    /**
     * Remove attachment file from storage, info about it from DB and thumb file if it exists
     * @return bool|null
     */
    public function deleteAttachmentFile( $deleteWithTheThumbnail = true )
    {
        $result = Storage::disk('local')->delete($this->file_name);

        // except the default attachment thumb (for local environment without ffmpeg)
        if ( $result && $deleteWithTheThumbnail && $this->thumb_file && $this->thumb_file != self::DIR_NAME_ATTACHMENT_THUMBS .'/'.self::DEFAULT_THUMB_FILE_NAME ) {
            $result = Storage::disk('local')->delete($this->thumb_file);
        }

        return $result ? $this->delete() : $result;
    }

    /**
     * Get the url for the Attachment thumb image
     * @return \Illuminate\Contracts\Routing\UrlGenerator|string
     */
    public function getThumbImageLink()
    {
        return $this->thumb_file ? url( Storage::url($this->thumb_file) ) : url( self::DEFAULT_THUMB_FILE_NAME);
    }

    /**
     * Get full path to the attachment file
     * @return null|string
     */
    public function getFilePath()
    {
        return $this->file_name ? Storage::disk('local')->path($this->file_name): null;
    }

    /**
     * Get info about Attachment in array to display it on front-end
     * @param array $purchasedAttachmentsArray
     * @return array
     */
    public function getInfoForFrontEnd(array $purchasedAttachmentsArray = [] )
    {

        return [
            'id'    => $this->id,
            'title' => $this->getShortTitleName(),
            'thumb' => $this->getThumbImageLink(),
            'price' => $this->price,
            'duration' => $this->getDuration(),
            'payed' => in_array($this->id, $purchasedAttachmentsArray),
            //'downloadUrl' => route('')
            'type'          => $this->type,
            'is_approved' => $this->is_approved,
        ];
    }

    /**
     * Get info about Attachment in array to display it on front-end
     * @param array $purchasedAttachmentsArray
     * @return array
     */
    public function getExtendedInfoForFrontEnd( $purchasedAttachmentsArray = [] )
    {

        $isPurchased = in_array($this->id, (array)$purchasedAttachmentsArray);

        /** @var User $model */
        $model          = $this->model;
        $modelProfile   = $model->userModelProfile;

        return [
            'id'            => $this->id,
            'added_at'      => $this->created_at,
            'model_id'      => $this->user_id,
            'title'         => $this->getShortTitleName(),
            'thumb'         => $this->getThumbImageLink(),
            'price'         => $this->price,
            'payed'         => $isPurchased,
            'downloadUrl'   => ($isPurchased || (int)$this->price === 0)  ? route('download-file', ['file_id' => $this->id]) : null,
            'watchUrl'      => ($isPurchased || (int)$this->price === 0)  ? route('attachment-source', ['attachment' => $this->id]) : null,
            'mimeType'      => $this->getMimeType(),
            'duration'      => $this->getDuration(),
            'modelAvatar'   => $modelProfile->getAvatarLink(),
            'profileName'   => $modelProfile->getProfileName(),
            'profileUrl'    => $modelProfile->getProfileLink(),
            'type'          => $this->type,
            'visibleForFans' => $this->visible_for_fans,
            'is_approved' => $this->is_approved,
        ];
    }

    /**
     * Get array for Admin Attachments page
     * @return array
     */
    public function getExtendedInfoForAdmin()
    {
        /** @var User $model */
        $model          = $this->model;
        $modelProfile   = $model->userModelProfile;

        return [
            'id'            => $this->id,
            'model_id'      => $this->user_id,
            'title'         => $this->getShortTitleName(),
            'thumb'         => $this->getThumbImageLink(),
            'price'         => $this->price,
            'payed'         => $this->attachmentBuyings->count(),
            'downloadUrl'   => route('download-file', ['file_id' => $this->id]),
            'watchUrl'      => route('attachment-source', ['attachment' => $this->id]),
            'mimeType'      => $this->getMimeType(),
            'duration'      => $this->getDuration(),
            'modelAvatar'   => $modelProfile->getAvatarLink(),
            'profileName'   => $modelProfile->getProfileName(),
            'profileUrl'    => $modelProfile->getProfileLink(),
            'added_at'       => $this->created_at,
            'type'          => $this->type,
            'visibleForFans' => $this->visible_for_fans,
            'is_approved' => $this->is_approved,
        ];
    }

    /**
     * Method to buy this Attachment by specified Fan
     * @param User $fan
     * @return bool
     */
    public function buyThisAttachment( User $fan )
    {
        /** @var User $model */
        $model = $this->model;

        $newTransaction = new TokensTransaction();

        if ( $newTransaction->makeTransaction($fan, $model, (integer)$this->price, TokensTransaction::TRANSACTION_TYPE_BUY_ATTACHMENT) ) {

            $newAttachmentBuying = new FanAttachmentBuying();
            $newAttachmentBuying->saveAttachmentBuying( $newTransaction , $this->id);

            event( new AttachmentSoldToClientEvent(['twitterMessageEvent' => TwitterMessageText::EVENT_VIDEO_SOLD_TO_CLIENT], $model) );

            return true;
        }else{
            $this->errors = $newTransaction->errors;
        }

        return false;
    }

    /**
     * Make the simple Attachment output to the browser
     */
    public function simpleOutput()
    {
        header("Content-Length:" . Storage::size($this->file_name) );
        header("Content-Type: " . $this->getMimeType() );

        echo Storage::get( $this->file_name );
    }

    /**
     * Calculate the video duration
     * @return false|null|string
     */
    public function calculateDuration()
    {

        if($this->type !== 'video') {
            $this->video_duration = 'picture';

            return 'picture';
        }

        $realFilePath = $this->getFilePath();

        if ( File::exists( $realFilePath ) ) {

            $getID3 = new \getID3;
            $file = $getID3->analyze( $realFilePath );
            $this->video_duration = $file['playtime_seconds'] ?? 0 ;

            return $this->video_duration;
        }

        return 0;
    }

    /**
     * Get the video duration
     * @param string $format
     * @return false|null|string
     */
    public function getDuration($format = 'i:s' )
    {
        if($this->type === 'picture'){

            return  'picture';
        }

        return date($format, (float)($this->video_duration ?? $this->calculateDuration()));
    }



    /**
     * Get the file size converted to the string
     * @return string
     */
    public function getFileSizeToString()
    {
        $fileSize = Storage::size($this->file_name);

        return self::convertBytesFileSize( $fileSize );
    }

    /**
     * Returns the file size in string format (with kB, MB units)
     * @param $file
     * @return string
     */
    public static function fileSizeToString( $file )
    {
        $fileSize = $file->getSize();

        return self::convertBytesFileSize( $fileSize );
    }

    /**
     * Return converted file size (from bytes to kB , MB)
     * @param null $fileSize
     * @return string
     */
    public static function convertBytesFileSize( $fileSize = null )
    {
        $fileSize = is_null($fileSize) ? self::MAX_FILE_SIZE_IN_BYTES : $fileSize;

        if ( $fileSize > 1000000 ) {
            return round($fileSize/1000000, 0, PHP_ROUND_HALF_DOWN). ' MB';
        } elseif ($fileSize > 1000) {
            return round($fileSize/1000, 0, PHP_ROUND_HALF_DOWN). ' kB';
        } else {
            return $fileSize.' bytes';
        }
    }

    /**
     * Get the file short name
     * @return string
     */
    public function getFileShortName()
    {
        $fileName = basename($this->file_name);

        $dotPosition = strpos($fileName, '.');

        if ( $dotPosition < 7 ) {

            return $fileName;
        }

        $extension = mb_substr($fileName, $dotPosition+1);

        return mb_substr($fileName, 0, 7) . '...' . $extension;
    }

    /**
     * Returns the file name for ajax response
     * @param $file
     * @return string
     */
    public static function fileShortName(UploadedFile $file )
    {
        $fileName = $file->getClientOriginalName();

        $dotPosition = strpos($fileName, '.');

        if ( $dotPosition < 7 ) {
            return $fileName;
        }

        return mb_substr($fileName, 0, 7) . '...' . $file->getClientOriginalExtension();
    }

    /**
     * Get all available attachments
     * @param array $excludedAttachments
     * @return Builder|static[]
     */
    public static function getAll($excludedAttachments = [] )
    {
        if ( !empty($excludedAttachments) ) {
            $query = self::whereNotIn('attachments.id', $excludedAttachments)->with('model');
        }else{
            $query = self::query()->with('model');
        }

        $query
            ->where('is_approved', '=', 1)
            ->with(['model', 'model.userModelProfile'])
            ->whereHas('model', function($query) {
                $query->where([
                    ['validation', '=', User::VALIDATION_CONFIRMED],
                    ['status', '=', User::STATUS_ENABLED],
                ]);
            })
            ->orderBy('attachments.created_at', 'desc');

        return $query;
    }

    /**
     * Get the attachment available for the Fan by subscription on the Model
     * @param User $fan
     * @param array $excludedAttachments
     * @return mixed
     */
    public static function getAttachmentsOfSubscribedModels(User $fan, array $excludedAttachments = [] )
    {
        /** @var int $fanId */
        $fanId = $fan->id;

        $query = self::join('tokens_models_subscriptions', 'tokens_models_subscriptions.model_id', '=', 'attachments.user_id')
            ->where([
                ['tokens_models_subscriptions.fan_id', '=', $fanId],
                ['tokens_models_subscriptions.expire_at', '>', now()],
            ]);

        return !empty($excludedAttachments) ? $query->whereNotIn('id', $excludedAttachments)->get() : $query->get();
    }

    /**
     * Method to search among the Attachments by specific text
     * @param $stringToSearch
     * @return mixed
     */
    public static function searchAllByText( $stringToSearch )
    {
        return self::selectRaw('attachments.*')
            ->where('tags', 'like', '%'.$stringToSearch.'%')
            ->join('user_model_profiles', 'user_model_profiles.user_id', '=', 'attachments.user_id')
            ->orWhere('description', 'like', '%'.$stringToSearch.'%')
            ->orWhere('title', 'like', '%'.$stringToSearch.'%')
            ->orWhere('user_model_profiles.artistic_name', 'like', '%'.$stringToSearch.'%')

            ->with(['model', 'model.userModelProfile'])
            ->get();
    }

    /**
     * Get the merged collection of purchased or subscribed Model's attachments for specified fan (if was passed)
     * @param User|null $fan
     * @return array|Collection
     */
    public static function getForFanPurchasedOrSubscribedAttachments(User $fan = null )
    {
        $mergedCollection = [];

        if ( $fan ) {

            $attachmentsPurchasedByFan           = $fan->fansAttachments;
            $purchasedAttachmentIdsArray         = $attachmentsPurchasedByFan->pluck('id')->toArray();

            $attachmentsOfSubscribedModels   = Attachment::getAttachmentsOfSubscribedModels( $fan, $purchasedAttachmentIdsArray );

            $mergedCollection           = $attachmentsOfSubscribedModels->merge( $attachmentsPurchasedByFan );

            // remove that relation to avoid  Auth::user() serialization error
            $fan->unsetRelation('fansAttachments');
        }

        return $mergedCollection;
    }

    /**
     * Get the collection of the Model's Attachments by specified ids
     *
     * @param array $idsArray
     * @return mixed
     */
    public static function getByIds($idsArray = [])
    {
        return self::whereIn('id', $idsArray)
            ->with(['model', 'model.userModelProfile'])
            ->get()
        ;
    }

    /**
     * Get array of the Fan's purchased or subscribed Model's Attachments
     *
     * @param User $fan
     * @return Collection
     */
    public static function getIdsArrayOfPurchasedOrSubscribedAttachmentsForFan(User $fan = null )
    {
        $array = [];

        if ( $fan ) {

            $query = DB::select( DB::raw('
            SELECT fab.attachment_id attachment_id
            FROM fan_attachment_buyings fab
            WHERE fab.fan_id = ?
            UNION (
              SELECT a.id
              FROM attachments a
                INNER JOIN tokens_models_subscriptions tms ON tms.model_id = a.user_id
              WHERE tms.fan_id = ? AND tms.expire_at > ? AND tms.is_free = ? and a.visible_for_fans = ?
            )
        '), [ $fan->id, $fan->id, now(), false, 1]);

            return collect($query)->pluck('attachment_id');

        }

        return collect([]);
    }

    /**
     * Convert the attachments collection to the array for the Front-end extended format
     * @param $attachments
     * @param array|null|Collection $purchasedAttachmentsIdsArray
     * @return Collection
     */
    public static function getInfoInArrayForFrontEnd($attachments, $purchasedAttachmentsIdsArray = [] )
    {
        $resultAttachmentArray = [];

        /** @var Attachment $attachment */
        foreach ($attachments as $attachment) {

//            if ( env('APP_DEBUG') === false ) {
//                $attachment->model->userModelProfile->setRelation('user', $attachment->model);
//            }
            /** @var User $editor */
            $editor = Auth::user();

            if ( !Auth::guest() && $editor->role() === User::ROLE_ADMIN ) {
                $resultAttachmentArray[] = $attachment->getExtendedInfoForAdmin();
            }else{
                $resultAttachmentArray[] = $attachment->getExtendedInfoForFrontEnd( $purchasedAttachmentsIdsArray );
            }
        }
        return collect($resultAttachmentArray);
    }

    /**
     * @param $attachmentsData
     * @return AnonymousResourceCollection
     */
    public static function prepareDataForFrontend($attachmentsData)
    {
        return PictureOrVideoResource::collection( $attachmentsData );
    }

    /**
     * Get Models attachment in array with extended data
     * @param $attachment
     * @param User|null $fan
     * @return AnonymousResourceCollection
     */
    public static function getInfoInArrayForFrontendForFan($attachment, User $fan = null )
    {
        $purchasedAndSubscribedAttachmentsIds   = Attachment::getIdsArrayOfPurchasedOrSubscribedAttachmentsForFan( $fan );
        session()->put('purchasedAttachmentsForFan '. ($fan->id ?? ''), $purchasedAndSubscribedAttachmentsIds);
        return PictureOrVideoResource::collection( $attachment );
    }

    /**
     * Returns the short version of the Attachment title (of full if it's length less than 26 signs)
     * @return mixed
     */
    protected function getShortTitleName()
    {
        return strlen($this->title) > 26 ? mb_substr($this->title,0, 26) . '...' : $this->title;
    }

    public function isPurchased()
    {
        return (bool)$this->attachmentBuyings()->count();
    }

    /**
     * Check if user can view attachment
     * @return bool
     */
    public function canUserViewAttachment(): bool
    {
        /** @var User $fan */
        $fan = Auth::user();

        if(!$fan){
            return false;
        }

        $modelId = $this->user_id;

        return $fan->fansAttachments->contains($this)
            || $fan->id === $modelId
            || $fan->role() === User::ROLE_ADMIN
            || TokensModelsSubscription::checkFanHasSubscriptionOnModel($fan->id, $modelId)
            || (int)$this->price === 0;
    }

    public function getPendingFiles()
    {

        return $this->where('is_approved', '=', 0)->get();
    }

    public static function getPendingFilesCount()
    {
        return self::where('is_approved', '=', 0)->count();
    }

}