<?php

namespace App\Traits;

use Illuminate\Support\Collection;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Relations\MorphTo;

use App\Models\PageAccess;
use App\Models\ContentElement;
use App\Models\Version;
use App\Utilities\SearchResult;
use App\Models\Contentable;

use App\Models\TextBlock;
use App\Models\Slideshow;
use App\Models\PhotoBlock;
use App\Models\Quote;
use App\Models\YoutubeVideo;
use App\Models\CourseList;

use App\Models\Tag;
use App\Models\Photo;
use App\Utilities\PhotoLink;

trait HasContentElementsTrait
{
    public function contentElements()
    {
        return $this->morphToMany(ContentElement::class, 'contentable')->withPivot(
            'sort_order',
            'unlisted',
            'expandable',
            'version_id',
            'deleted_at',
            'guest',
            'no_margin',
            'randomize',
            'filter',
            'hide_print',
        );
    }

    public function contentables()
    {
        return $this->morphMany(Contentable::class, 'contentable');
    }

    public function getTypeAttribute()
    {
        return Str::kebab(class_basename($this));
    }

    public function getFullTypeAttribute()
    {
        return get_class($this);
    }

    public function getResourceAttribute()
    {
        return Str::kebab(Str::plural(class_basename($this)));
    }

    public function saveContentElements($input)
    {
        if (Arr::get($input, 'content')) {
            foreach (Arr::get($input, 'content') as $content_element) {
                $content_element = (new ContentElement())->saveContentElement($content_element, Arr::get($content_element, 'id'));
            }
        }

        return $this;
    }

    public function getContentAttribute()
    {
        if ((auth()->user()?->can('update', $this) || request()->hasValidSignature()) && request('preview')) {
            $content_elements = ContentElement::filterByPermissions($this->preview_content_elements);
        } elseif (auth()->user()?->can('update', $this) && editing()) {
            $content_elements = ContentElement::filterByPermissions($this->all_content_elements);
        } else {
            if (auth()->check()) {
                $content_elements = cache()->tags([cache_name($this), cache_name(auth()->user())])->rememberForever(cache_name($this).'-user-'.auth()->user()->id.'-content-elements', function () {
                    return ContentElement::filterByPermissions($this->published_content_elements);
                });
            } else {
                $content_elements = cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-guest-content-elements', function () {
                    return ContentElement::filterByPermissions($this->published_content_elements);
                });
            }
        }

        if ($this->version?->randomize && (!editing() || request('preview'))) {
            $content_elements = cache()->tags([cache_name($this)])->remember(cache_name($this).'-randomized-'.(request('preview') ? Str::random(10) : 'published'), (60 * 60 * 24), function() use($content_elements) {
                $random_content_elements = collect();
                $non_random_content_elements = collect();

                $content_elements->each(function($content_element) use($random_content_elements, $non_random_content_elements) {
                    if($content_element->pivot->randomize) {
                        $random_content_elements->push($content_element);
                    } else {
                        $non_random_content_elements->push($content_element);
                    }
                });

                if ($random_content_elements->count() > $this->version->randomize) {
                    $random_content_elements = $random_content_elements->random($this->version->randomize)->values();
                }

                $random_sort_numbers = $random_content_elements->map(function($ce) {
                    return $ce->pivot->sort_order;
                })->shuffle();

                return $non_random_content_elements->merge($random_content_elements)->sortBy(function($ce) use($random_sort_numbers) {
                    if (!$ce->pivot->randomize) {
                        return ($ce->pivot->expandable === 'modal' ? 'zzz-' : '').$ce->pivot->sort_order;
                    }
                    return $random_sort_numbers->pop();
                })->values();
            });
        }
        return $content_elements;
    }

    public function allContent($version_id = null, $published = null, $preview = null)
    {
        $cache_name = cache_name($this).'-published-content-element';

        $content_elements = $this->contentElements()->with([
            'tags',
            'contentables.version',
            'content' => function (MorphTo $morphTo) {
                $morphTo->morphWith([
                    TextBlock::class => ['photos', 'photos.fileUpload'],
                    Slideshow::class => ['photos', 'photos.fileUpload'],
                    PhotoBlock::class => ['photos', 'photos.fileUpload'],
                    Quote::class => ['photos', 'photos.fileUpload'],
                    YoutubeVideo::class => ['photos', 'photos.fileUpload'],
                    CourseList::class => ['tags'],
                ]);
            },
        ]);

        if ($published) {
            $content_elements->whereHas('versions', function ($query) {
                $query->whereNotNull('published_at');
            });
        }

        $content_elements = $content_elements->get();

        if ($version_id) {
            $content_elements = $content_elements->filter(function ($content_element) use ($version_id) {
                return $content_element->pivot->version_id <= $version_id ? true : false;
            });
        }

        if (cache()->has($cache_name)) {
            return cache()->get($cache_name);
        }

        $content_elements = $content_elements->groupBy('uuid')
            ->map(function ($uuid) {
                return $uuid->sortByDesc(function ($content_element) {
                    return $content_element->pivot->version_id;
                })->first();
            })
            ->filter(function ($content_element) use ($published, $preview) {
                if ($published || $preview) {
                    if ($content_element->pivot->deleted_at || $content_element->pivot->unlisted) {
                        return false;
                    }
                } else {
                    if ($content_element->pivot->deleted_at) {
                        return false;
                    }
                }
                return true;
            })
            ->sortBy(function ($content_element) {
                return $content_element->pivot->sort_order;
            })->values()
             ->each(function ($content_element) {
                 $content_element->append((new ContentElement())->appendWith());
                 $content_element->content->appendAttributes();
             });

        if ($published) {
            cache()->tags([cache_name($this)])->forever($cache_name, $content_elements);
        }

        return $content_elements;
    }

    public function getAllContentElementsAttribute()
    {
        return $this->allContent(request('version_id'));
    }

    public function getPublishedContentElementsAttribute()
    {
        return $this->allContent(request('version_id'), true);
    }

    public function getPreviewContentElementsAttribute()
    {
        return $this->allContent(request('version_id'), null, true);
    }
    public function getPhotosAttribute()
    {
        return $this->content->map(function ($content) {
            return $content->content->photos->sortBy('sort_order');
        })
        ->flatten()
        ->unique();
    }

    public function getPhotoAttribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-menu-photo', function () {
            if (!$this->photos->count()) {
                return null;
            }
            return $this->photos->first();
        });
    }

    public function getPhotoLinkAttribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-photo-link', function () {
            if (!$this->photos->count()) {
                return null;
            }
            $photo = $this->photo;

            $photo_link = new PhotoLink(
                $photo->id,
                $photo->file_upload_id,
                $photo->small,
                $photo->medium,
                $photo->large,
                $photo->retina,
                $photo->alt,
                $photo->name,
                $photo->description,
                $photo->width,
                $photo->height,
                $photo->offsetX,
                $photo->offsetY,
                $this->full_slug,
                $this->name,
            );

            return $photo_link;
        });
    }

    public function getTeaserAttribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-menu-teaser', function () {
            return $this->content->map(function ($content) {
                return $content->content->body;
            })
                ->flatten()
                ->filter()
                ->first();
        });
    }

    public function getSearchFieldsAttribute()
    {
        return (new Version())->search_fields;
    }

    public static function searchForItems($terms)
    {
        return self::where(function ($query) use ($terms) {
            $query->whereHas('versions', function ($query) use ($terms) {
                if (!editing()) {
                    $query->whereNotNull('published_at');
                }

                $first = true;
                $query->where(function ($query) use ($terms, $first) {
                    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.'%');
                            }
                        }
                    }
                });
            })
            ->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.'%');
                    }
                }
            })
            ->orWhereHas('pageSlugs', function ($query) use ($terms) {
                $first = true;
                foreach ($terms as $term) {
                    if ($first) {
                        $query->where('slug', 'LIKE', '%'.$term.'%');
                        $first = false;
                    } else {
                        $query->orWhere('slug', 'LIKE', '%'.$term.'%');
                    }
                }
            });
        })
        ->with('publishedVersion')
        ->get();
    }

    public static function searchResults($terms)
    {
        $pages = self::searchForItems($terms);

        $auth = auth()->check();

        $pages = $pages->filter(function ($page) use ($auth) {
            if ($auth) {
                if (!$page->published_at && !auth()->user()->can('update', $page)) {
                    return false;
                }
                if ($page->hasViewPermissions()) {
                    if (!auth()->user()->can('view', $page)) {
                        return false;
                    }
                }
            } else {
                if ($page->hasViewPermissions()) {
                    return false;
                }
            }

            if (optional($page->publishedVersion)->search_exclude) {
                return false;
            }
            return true;
        });

        $results = collect();

        foreach ($pages as $page) {
            $search_result = new SearchResult(
                $page->type,
                $page->id,
                $page->full_slug,
                $page->getSearchResultTitle(),
                $page->getSearchResultPreview($terms),
                $page->getSearchResultRank($terms),
                SearchResult::urlText($page->full_slug),
                $page->published_at ?? $page->updated_at,
                $page->getSearchResultPhoto($terms),
            );
            $results->push($search_result);
        }

        return $results;
    }

    public function getSearchResultTitle($default = null)
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-search-title', function() use($default) {
            return $this->version?->title ?? $this->name;
        });
    }

    public function getSearchResultPreview($terms)
    {
        $content_elements = $this->content;
        /*
        if (!auth()->check()) {
            $content_elements = $this->published_content_elements;
        } else {
            if (auth()->user()->can('update', $this)) {
                $content_elements = $this->all_content_elements;
            } else {
                $content_elements = $this->published_content_elements;
            }
        }
         */

        $preview_text = '';

        foreach ($content_elements as $content_element) {
            if (mb_strlen($preview_text) < 1) {
                if (method_exists($content_element->content, 'getSearchResultPreview')) {
                    $text = $content_element->content->getSearchResultPreview($terms);
                    if (mb_strlen($text) > 0) {
                        $preview_text = $text;
                    }
                }
            }
        }

        return $preview_text;
    }

    public function getSearchResultRank($terms)
    {
        $rank = 5;

        foreach ($terms as $term) {
            foreach ((new self())->search_fields as $field) {
                if (optional($this->version)->{$field}) {
                    if ($field === 'name') {
                        $words = collect(explode(' ', mb_strtolower($this->version->name)));
                        if ($words->contains(mb_strtolower($term))) {
                            $multiplier = $this->type === 'page' ? 150 : 100;
                        } else {
                            $multiplier = $this->type === 'page' ? 25 : 15;
                        }
                    } else {
                        $multiplier = $this->type === 'page' ? 15 : 10;
                    }
                    $score = mb_substr_count(mb_strtolower($this->version->{$field}), mb_strtolower($term));
                    $rank += ($score * $multiplier);
                }
            }

            foreach ($this->content as $ce) {
                if (method_exists($ce->content, 'getSearchResultRank')) {
                    $rank += $ce->content->getSearchResultRank($terms);
                }
            }
        }

        return $rank;
    }

    public function getSearchResultPhoto($terms)
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-search-photo', function() use($terms) {
            $photos = $this->content->map(function ($content_element) {
                if ($content_element->content->photos) {
                    return $content_element->content->photos->first();
                } else {
                    return null;
                }
            })->filter();

            if ($photos->count()) {
                $photo = $photos->first(function ($photo) use ($terms) {
                    if ($photo->name) {
                        if (Str::contains($photo->name, $terms)) {
                            return true;
                        }
                    }

                    if ($photo->description) {
                        if (Str::contains($photo->description, $terms)) {
                            return true;
                        }
                    }

                    if ($photo->alt) {
                        if (Str::contains($photo->alt, $terms)) {
                            return true;
                        }
                    }

                    return false;
                });
            } else {
                $photo = null;
            }

            return $photo ?? $photos->first();
        });
    }
}
