<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Builder;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use App\Http\Requests\PhotoValidation;

use Intervention\Image\Laravel\Facades\Image;

use App\Models\Page;
use App\Models\FileUpload;
use App\Models\ContentElement;
use App\Utilities\PageLink;
use App\Traits\TagsTrait;

use App\Models\EmbedVideo;
use App\Models\PhotoBlock;
use App\Models\TextBlock;
use App\Models\YoutubeVideo;
use App\Models\InlinePhoto;
use App\Models\Quote;
use App\Models\InquiryForm;
use App\Models\Slideshow;

use App\Contracts\SearchResultContract;
use App\Utilities\SearchResult;

use App\Jobs\CreatePhotoImage;

class Photo extends Model implements SearchResultContract
{
    use HasFactory;
    use SoftDeletes;
    use TagsTrait;

    protected $with = ['fileUpload', 'tags'];
    protected $appends = ['offset_class'];
    protected $casts = [
        'offsetX' => 'string',
        'offsetY' => 'string',
        'fill' => 'boolean',
        'enlarge' => 'boolean',
        'hide_print' => 'boolean',
        'hide_mobile' => 'boolean',
        'large_link' => 'boolean',
        'border' => 'boolean',
    ];

    public function savePhoto(array $input, $id = null, $content = null, $duplicate = null)
    {
        if (!$input) {
            return null;
        }

        $validation = Validator::make($input, (new PhotoValidation())->rules());
        if ($validation->fails()) {
            // TODO we should throw a 422 here probably
            if (!env('production')) {
                //dd($validation->errors()->all());
                throw ValidationException::withMessages($validation->errors()->all());
            }
            return null;
        }

        if ($id >= 1) {
            if ($duplicate) {
                $photo = new Photo();
            } else {
                $photo = Photo::findOrFail($id);
            }

            if (!$content) {
                $content = $photo->content;
            }
        } else {
            // we need to check for the same filename
            // in the content element as we can save
            // the content element while the image is still processing

            if ($content->photos()->count()) {
                $existing_photo = $content->photos()->get()->filter(function ($photo) use ($input) {
                    return $photo->fileUpload->id === Arr::get($input, 'file_upload.id');
                })->first();

                if ($existing_photo) {
                    $photo = $existing_photo;
                } else {
                    $photo = new Photo();
                }
            } else {
                $photo = new Photo();
            }

            // flush the cache for the photo form
            cache()->tags(['photos'])->flush();
        }

        $photo->content_id = $content->id;
        $photo->content_type = get_class($content);

        $file_upload = FileUpload::findOrFail(Arr::get($input, 'file_upload_id'));

        if (!$photo->width || !$photo->height) {
            $storage_filename = Storage::get($file_upload->storage_filename);
            if ($storage_filename) {
                $info = Image::read($storage_filename);
                $photo->width = $info->width();
                $photo->height = $info->height();
            }
        }

        if (Arr::get($input, 'name')) {
            $name = Arr::get($input, 'name');
        } else {
            $name = $file_upload->name;
        }

        if ($photo->fileUpload) {
            if ($photo->fileUpload->id !== $file_upload->id) {
                $photo->fileUpload()->delete();
                $photo->removeImages();
            }
        }

        $photo->file_upload_id = $file_upload->id;

        $photo->name = $name;
        $photo->description = Arr::get($input, 'description');
        $photo->alt = Arr::get($input, 'alt');

        $photo->sort_order = Arr::get($input, 'sort_order');
        $photo->span = Arr::get($input, 'span');
        $photo->vertical_span = Arr::get($input, 'vertical_span');
        $photo->offsetX = Arr::get($input, 'offsetX');
        $photo->offsetY = Arr::get($input, 'offsetY');
        $photo->fill = Arr::get($input, 'fill');
        $photo->enlarge = Arr::get($input, 'enlarge');
        $photo->hide_print = Arr::get($input, 'hide_print');
        $photo->hide_mobile = Arr::get($input, 'hide_mobile');
        $photo->title = Arr::get($input, 'title');
        $photo->subtitle = Arr::get($input, 'subtitle');
        $photo->link = Arr::get($input, 'link');
        $photo->border = Arr::get($input, 'border');
        $photo->large_link = Arr::get($input, 'large_link');
        $photo->number = Arr::get($input, 'number');

        $photo->save();

        $old_tags = $photo->tags;

        $photo->saveTags($input);

        $photo->refresh();

        if ($old_tags->pluck('id')->intersect($photo->tags->pluck('id')) || !$id) {
            cache()->tags(['photos'])->flush();
        }

        cache()->tags([cache_name($photo)])->flush();

        return $photo;
    }

    public function getOffsetClassAttribute() 
    {
        return 'object-'.$this->roundOffset($this->offsetX).'-'.$this->roundOffset($this->offsetY);   
    }

    private function roundOffset($number) {
        return (int) 5 * round($number / 5);
    }

    public function content()
    {
        return $this->morphTo();
    }

    public function fileUpload()
    {
        return $this->belongsTo(FileUpload::class);
    }

    public function createImage($size, $name)
    {
        if (!$this->fileUpload) {
            $file_name = '/images/default.png';
        //Log::warning('PHOTO '.$this->id.' NO UPLOAD');
        } elseif (Storage::missing($this->fileUpload->storage_filename)) {
            // TODO we should check for orphans here
            //Log::warning('PHOTO '.$this->id.' MISSING UPLOAD');
            $file_name = '/images/default.png';
        } else {
            $file = Storage::get($this->fileUpload->storage_filename);
            $file_name = Str::replace(' ', '-', '/photos/'.$this->fileUpload->id.'/'.$name.'-'.$this->fileUpload->name);

            if (Storage::disk('public')->missing($file_name)) {
                //Log::info('CREATE PHOTO '.$this->id.'::'.$file_name);
                $image = Image::read($file)
                    ->scale($size);

                Storage::disk('public')->put($file_name, $image->toJpeg(80));
                Storage::disk('public')->put($file_name.'.webp', $image->toWebp(80));
            }
        }

        $this->{$name} = $file_name;
        $this->save();
        cache()->tags(['photos'])->flush();
        return $file_name;
    }

    public function getSmallAttribute($value)
    {
        if (!$this->fileUpload) {
            return null;
        }
        if (!$value) {
            //Log::info('PHOTO '.$this->id.':: No SMALL value');
            CreatePhotoImage::dispatch($this, 'small');
        } elseif (Storage::disk('public')->missing($value)) {
            //Log::info('PHOTO '.$this->id.':: No SMALL public file');
            CreatePhotoImage::dispatch($this, 'small');
        }
        return $value;
    }

    public function getMediumAttribute($value)
    {
        if (!$this->fileUpload) {
            return null;
        }
        if (!$value) {
            //Log::info('PHOTO '.$this->id.':: No MEDIUM value');
            CreatePhotoImage::dispatch($this, 'medium');
        } elseif (Storage::disk('public')->missing($value)) {
            //Log::info('PHOTO '.$this->id.':: No MEDIUM public file');
            CreatePhotoImage::dispatch($this, 'medium');
        }
        return $value;
    }

    public function getLargeAttribute($value)
    {
        if (!$this->fileUpload) {
            return null;
        }
        if (!$value) {
            //Log::info('PHOTO '.$this->id.':: No LARGE value');
            return $this->createImage(1152, 'large');
        } elseif (Storage::disk('public')->missing($value)) {
            //Log::info('PHOTO '.$this->id.':: No LARGE public value');
            return $this->createImage(1152, 'large');
        }
        return $value;
    }

    public function getRetinaAttribute($value)
    {
        if (!$this->fileUpload) {
            return null;
        }
        if (!$value) {
            //Log::info('PHOTO '.$this->id.':: No RETINA value');
            CreatePhotoImage::dispatch($this, 'retina');
        } elseif (Storage::disk('public')->missing($value)) {
            //Log::info('PHOTO '.$this->id.':: No RETINA public value');
            CreatePhotoImage::dispatch($this, 'retina');
        }
        return $value;
    }

    public function getLinkAttribute($value)
    {
        return PageLink::convertLink($value);
    }

    public static function searchResults($terms)
    {
        $auth = auth()->check();

        $photos = self::where(function ($query) use ($terms) {
            $first = true;
            foreach ((new self())->search_fields as $field) {
                foreach ($terms as $term) {
                    if ($first) {
                        $query->where($field, 'LIKE', '%'.$term.'%');
                        $first = false;
                    } else {
                        $query->orWhere($field, 'LIKE', '%'.$term.'%');
                    }
                }
            }
            /*
            $query->orWhereHas('tags', function ($query) use ($terms) {
                $first = true;
                foreach ($terms as $term) {
                    if ($first) {
                        $query->where('name', 'LIKE', '%'.$term.'%');
                        $first = false;
                    } else {
                        $query->orWhere('name', 'LIKE', '%'.$term.'%');
                    }
                }
            });
             */
        })
        //->with(['content', 'content.contentElement', 'content.contentElement.pages', 'content.contentElement.blogs'])
        ->with(['content' => function (MorphTo $morphTo) {
            $morphTo->morphWith([
                ContentElement::class => ['pages', 'blogs'],
            ]);
        }])
        ->get();

        $results = collect();

        foreach ($photos as $photo) {
            if ($photo->content?->contentElement) {
                foreach ($photo->content->contentElement->contentables as $contentable) {
                    $type = Str::plural(Str::lower(class_basename($contentable->contentable_type)));
                    $page = $photo->content->contentElement->{$type}->firstWhere('id', $contentable->contentable_id);

                    $version = $contentable->version;

                    if (($version->published_at && !$page->version->search_exclude) || ($auth ? auth()->user()->can('update', $contentable) : false)) {
                        $search_result = new SearchResult(
                            $page->type,
                            $page->id,
                            $page->full_slug,
                            $photo->getSearchResultTitle($page->getSearchResultTitle()),
                            $photo->getSearchResultPreview($terms),
                            $photo->getSearchResultRank($terms),
                            SearchResult::urlText($page->full_slug),
                            $page->published_at ?? $page->updated_at,
                        );

                        $results->push($search_result);
                    }
                }
            }
        }

        return $results;
    }

    public function getSearchFieldsAttribute()
    {
        return [
            'name',
            'description',
            'alt',
        ];
    }

    public function getSearchResultTitle($default = null)
    {
        return $this->content->getSearchResultTitle($default);
    }

    public function getSearchResultPreview($terms)
    {
        return $this->content->getSearchResultPreview($terms);
    }

    public function getSearchResultRank($terms)
    {
        $rank = 0;
        foreach ($terms as $term) {
            $rank += mb_substr_count($this->name, $term) * 0.5;
            $rank += mb_substr_count($this->description, $term) * 0.5;
            $rank += mb_substr_count($this->alt, $term) * 0.5;
        }
        return $rank;
    }

    public function regenerate($clear = false)
    {
        if ($clear) {
            $this->removeImages();
        }

        $this->refresh();
        cache()->tags([cache_name($this)])->flush();

        if (!$this->width || !$this->height) {
            $storage_filename = Storage::get($this->fileUpload->storage_filename);
            if ($storage_filename) {
                $info = Image::read($storage_filename);
                $this->width = $info->width();
                $this->height = $info->height();
                $this->save();
            }
        }

        $this->small = null;
        $this->medium = null;
        $this->large = null;
        $this->retina = null;
        $this->save();

        //$this->createImage(1152, 'large');
        CreatePhotoImage::dispatch($this, 'small');
        CreatePhotoImage::dispatch($this, 'medium');
        CreatePhotoImage::dispatch($this, 'large');
        CreatePhotoImage::dispatch($this, 'retina');
    }

    public function removeImages()
    {
        $images = collect([
            $this->small,
            $this->medium,
            $this->large,
            $this->retina,
        ])->filter();

        foreach ($images as $image) {
            if (Storage::disk('public')->exists($image)) {
                Storage::disk('public')->delete($image);
            }

            if (Storage::disk('public')->exists($image.'.webp')) {
                Storage::disk('public')->delete($image.'.webp');
            }
        }
    }

    public function viewable()
    {
        $user = auth()->user();

        if (!$user) {
            return false; // we only use this command for photo selection so not signed in, no access
            /*
            return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-guest-viewable', function() {
                if ($this->content->contentElement?->hasViewPermissions()) {
                    return false;
                }

                if ($this->content->contentElement?->contentables) {
                    $view = false;
                    foreach ($this->content->contentElement->contentables as $contentable) {
                        if ($contentable->pageable) {
                            if (!$contentable->pageable->hasViewPermissions()) {
                                $view = true;
                            }
                        }
                    }
                    return $view;
                }

                return false;
            });
             */
        }

        return cache()->tags([cache_name($this), cache_name($user)])->rememberForever(cache_name($this).'-'.cache_name($user).'-viewable', function () use ($user) {
            return $user->can('view', $this->content->contentElement);
        });
    }

    public static function getPhotos($tags = null)
    {
        if (!auth()->check()) {
            return collect();
        }

        $cache_name = 'photos-'.auth()->user()->id;

        if ($tags?->count()) {
            $cache_name .= '-'.$tags->map->id->join('-');
        }

        return cache()->tags(['photos'])->rememberForever($cache_name, function () use ($tags) {
            $photo_ids = Contentable::whereHasMorph('pageable', [Page::class])
                ->whereHas('contentElement', function ($query) use ($tags) {
                    $query->whereHasMorph(
                        'content',
                        [
                        EmbedVideo::class,
                        PhotoBlock::class,
                        TextBlock::class,
                        YoutubeVideo::class,
                        InlinePhoto::class,
                        Quote::class,
                        InquiryForm::class,
                        Slideshow::class,
                    ],
                        function ($query) use ($tags) {
                            $query->whereHas('photos', function ($query) use ($tags) {
                                if (!request('with_hidden')) {
                                    $query->whereNull('hidden_at');
                                }
                                if ($tags) {
                                    $query->whereHas('tags', function ($query) use ($tags) {
                                        $query->whereIn('tags.id', $tags->pluck('id')->toArray());
                                    });
                                }
                            });
                        }
                    );
                })
                ->with(['contentElement.content.photos'])
                ->get()
                ->map(function ($contentable) {
                    return $contentable->contentElement->content->photos->pluck('id')->toArray();
                })
                ->flatten();

            $photos = Photo::whereIn('id', $photo_ids)
                ->with(['content.contentElement.permissions.accessable', 'content.contentElement.contentables.pageable'])
                ->orderByDesc(
                    FileUpload::select('created_at')
                        ->whereColumn('id', 'photos.file_upload_id')
                        ->orderByDesc('created_at')
                        ->limit(1)
                )
                ->get()
                ->unique(function ($photo) {
                    return $photo->file_upload_id;
                })
                ->filter(function ($photo) {
                    return $photo->viewable();
                })
                ->values();

            return $photos;
        });
    }
}
