<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;

use Illuminate\Support\Facades\Storage;

use App\Models\ContentElement;
use App\Models\PageAccess;

use App\Traits\AppendAttributesTrait;
use App\Traits\VersioningTrait;
use App\Traits\HasContentElementsTrait;
use App\Traits\SlugTrait;
use App\Traits\TagsTrait;
use App\Traits\HasPermissionsTrait;

use App\Events\PageSaved;
use App\Utilities\Slug;
use App\Utilities\Breadcrumb;

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

class Page extends Model implements SearchResultContract
{
    use HasFactory;
    use SoftDeletes;
    use AppendAttributesTrait;
    use VersioningTrait;
    use HasContentElementsTrait;
    use SlugTrait;
    use TagsTrait;
    use HasPermissionsTrait;

    //protected $with = ['tags', 'publishedVersion', 'contentElements', 'pageSlugs', 'versions'];
    //protected $appends = ['pages', 'version', 'type', 'actions', 'has_permissions', 'full_slug', 'preview_content_elements'];

    protected $hidden = [
        'contentElements',
        'signed_url',
        'preview_url',
    ];

    public $append_attributes = [
        'actions',
        'full_type',
        'full_slug',
        'resource',
        'type',
        'name',
        //'version',
        //'can_be_published',
        //'breadcrumbs',
        //'sub_menu',
        //'published_at',
        //'preview_content_elements',
        //'pages',
    ];

    protected $casts = [
        'protected' => 'boolean',
    ];

    public function savePage(array $input, $id = null)
    {
        $old_slug_tag = null;
        $old_name = null;
        $old_parent = null;

        if ($id) {
            $page = Page::findOrFail($id);
            $old_slug_tag = Slug::getCacheString($page->full_slug);
            $old_name = $page->name;
            $old_parent = Page::find($page->version->parent_page_id);
        } else {
            $page = new Page();
        }

        if (auth()->user()->hasRole('admin')) {
            $page->protected = Arr::get($input, 'protected') == true ? true : false;
        }

        $page->save();

        $page->saveVersion($input);
        $page->saveContentElements($input);
        $page->saveTags($input);
        $page->refresh();

        if (!$id) {
            // TODO not a fan of these params
            // can we not just just make them optional
            // since we are calling the function with $this?
            $this->reorderPages($page->parentPage, $page);

            // create page permissions for the current user
            if (!auth()->user()->can('update', $page)) {
                $page->createPermission('update', auth()->user());
            }
        }

        if ($old_parent) {
            cache()->tags([cache_name($old_parent)])->flush();
        }

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

        if ($old_slug_tag) {
            cache()->tags([$old_slug_tag])->flush();
        }

        $slug_tag = Slug::getCacheString($page->full_slug);
        cache()->tags([$slug_tag])->flush();

        // if we have changed the name of one of the top level menu itmes clear the menu cache
        if ($page->version?->parent_page_id === 1 && !$page->version?->unlisted && $old_name !== $page->name) {
            cache()->tags(['nav-menu'])->flush();
        }

        if (!$id) {
            $page->publish();
        }

        broadcast(new PageSaved($page))->toOthers();

        return $page;
    }

    public static function getHomePage()
    {
        return Page::find(1);
    }

    /*
    public function getParentPage()
    {
        $version = $this->version;
        if ($version) {
            // TODO incase we somehow save the parent as itself
            if ($version->parent_page_id === $this->id) {
                $version->parent_page_id = 1;
                $version->save();
                cache()->tags([cache_name($this)])->flush();
                return Page::find(1);
            }
            return Page::find($version->parent_page_id);
        } else {
            // TODO we should never get here, unless its a new, unpublished page
            return Page::find(1);
        }
    }

    public function getParentPageAttribute()
    {
        return $this->getParentPage();
    }
     */

    public function getPagesAttribute()
    {
        return $this->getPages();
    }

    public function getPagesCountAttribute()
    {
        return $this->pages->count();
    }

    /*
    public function pages()
    {
        return $this->hasManyThrough(Page::class, Version::class, 'parent_page_id', 'id', 'id', 'versionable_id')
                    ->where('versionable_type', static::class)
                    ->whereHas('version')
                    ->with('version')
                    ->distinct();
    }
     */

    public function getPages()
    {
        return Page::with('version', 'publishedVersion')
            ->whereHas('version', function ($query) {
                $query->where('parent_page_id', $this->id);
            })
            ->get();

        /*
        $cache_name = $this->getCacheName('pages');
        //return cache()->tags([cache_name($this)])->rememberForever($cache_name, function () {
        $pages = Page::with('publishedVersion', 'versions')->whereHas('versions', function ($query) {
            $query->where('parent_page_id', $this->id);
        })
        ->get()
        ->filter(function ($page) {
            if (!$page->version) {
                return false;
            }
            return $page->version->parent_page_id === $this->id;
        });

        return $pages;
        //});
        //*/
    }

    public function getParentPageIdsAttribute()
    {
        if ($this->version?->parent_page_id < 1) {
            return null;
        }

        $cache_name = $this->getCacheName('parent-page-ids');
        return cache()->tags([cache_name($this)])->rememberForever($cache_name, function () {
            $parent_page_ids = collect();
            $parent_page = Page::find($this->version->parent_page_id);

            if ($parent_page instanceof Page) {
                while ($parent_page->id > 1) {
                    $parent_page_ids->push($parent_page->id);
                    $parent_page = $parent_page->parentPage;
                    if (!$parent_page) {
                        break;
                    }
                }
            }
            return $parent_page_ids->reverse()->values();
        });
    }

    protected function getCacheName($prop)
    {
        $cache_name = cache_name($this).'-'.$prop.'-'.auth()->user()?->id.(editing() ? '-editing' : '').(request('preivew') ? '-preview' : '');
    }

    public function getFullSlugAttribute()
    {
        $version_id = null;

        if (!editing() || request('preview')) {
            $version_id = $this->published_version_id;
        }

        if (!$version_id) {
            $version_id = $this->version?->id;
        }

        if (!$version_id) {
            return null;
        }

        $cache_name = cache_name($this).'-full-slug-v'.$version_id;

        if (editing()) {
            $cache_name .= '-editing';
        }

        $full_slug = cache()->tags([cache_name($this)])->rememberForever($cache_name, function () {
            $slug = $this->getSlug();

            if ($this->version->parent_page_id > 0) {
                //$parent_page = Page::find($this->version->parent_page_id);
                $parent_page = Page::where('id', $this->version->parent_page_id)->withOnly([])->first()?->setAppends([]);

                if ($parent_page instanceof Page) {
                    while ($parent_page->id > 1) {
                        $slug = $parent_page->getSlug().'/'.$slug;
                        $parent_page = $parent_page->parentPage;
                        if (!$parent_page) {
                            break;
                        }
                    }
                }
            }

            return $slug;
        });

        return $full_slug;
    }

    public function clearSlugCache()
    {
        cache()->tags(['slugs'])->flush();
        cache()->tags([cache_name($this)])->flush();

        foreach ($this->getPages() as $page) {
            $page->clearSlugCache();
        }
    }

    public static function publishScheduledContent()
    {
        $pages = Version::whereNull('published_at')
            ->where(function ($query) {
                $query->whereHasMorph('versionable', ['App\Models\Page', 'App\Models\Blog'], function ($query) {
                    $query->whereHas('contentElements', function ($query) {
                        $query->whereNotNull('publish_at')
                              ->where('publish_at', '<', now());
                    });
                })
                ->orWhere('publish_at', '<', now());
            })
            ->get()
            ->map(function ($version) {
                return $version->versionable;
            })
            ->each(function ($page) {
                $page->publish();
            });
    }

    public function getSubMenuAttribute()
    {
        return cache()->tags([cache_name($this), 'menu'])->rememberForever(cache_name($this).'-sub-menu', function() {
            return Page::with('version')
                ->whereHas('version', function ($query) {
                    $query->where('parent_page_id', $this->id)
                        ->where('unlisted', false);
                })
                ->get()
                ->sortBy(function ($page) {
                    return $page->version->sort_order;
                })
                ->values()
                ->append(['full_slug']);
        });

        /*
        if ($this->id !== 1) {
            return $this->getPages()
                         ->filter(function ($page) {
                             $version = $page->version;
                             return $version->published_at && !$version->unlisted;
                         })
                         ->sortBy(function ($page) {
                             return $page->version->sort_order;
                         })
                         ->values()
                         ->append(['full_slug', 'version']);
        }
         */
    }

    public function appendRecursive($attributes = null)
    {
        if (!$attributes) {
            $attribute = $this->append_attributes;
        }

        if (!is_array($attributes) && $attributes) {
            $attributes = [$attributes];
        }

        $this->appendAttributes($attributes);

        foreach ($this->pages as $page) {
            $page->appendRecursive($attributes);
        }
    }

    public function sortPages(Page $page, $input)
    {
        $old_parent_page = $page->parentPage;

        $draft_version = $page->getDraftVersion();
        $draft_version->sort_order = Arr::get($input, 'sort_order');
        $draft_version->parent_page_id = Arr::get($input, 'parent_page_id');
        $draft_version->save();

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

        $parent_page = Page::findOrFail(Arr::get($input, 'parent_page_id'));

        $this->reorderPages($parent_page, $page);

        if ($old_parent_page->id != Arr::get($input, 'parent_page_id')) {
            $this->reorderPages($old_parent_page, $page);
        }
    }

    protected function reorderPages(Page $parent_page, Page $page)
    {
        $pages = $parent_page->pages;

        $pages = $pages->reject(function ($p) use ($page) {
            return $p->id === $page->id;
        });

        $page->refresh();

        if ($page->version->parent_page_id === $parent_page->id) {
            $pages = $pages->push($page);
        }

        $pages = $pages->sortBy(function ($p) {
            $sort = sprintf('%07.1f', $p->version->sort_order).'-'.$p->created_at->getTimestamp();
            return $sort;
        })
        ->values()
        ->each(function ($p, $index) {
            $sort_order = (float) $index + 1;
            if ((float) $p->version->sort_order !== $sort_order) {
                $version = $p->getDraftVersion();
                $version->sort_order = $sort_order;
                $version->save();
                cache()->tags([cache_name($p)])->flush();
            }
        });
    }

    public function hasViewPermissions()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-has-view-permissions', function () {
            $version = $this->version;

            if (!$version) {
                return false;
            }

            if ($this->permissions()->where('action', 'view')->count()) {
                return true;
            }

            $has_view_permissions = false;

            if ($this->version->parent_page_id > 0) {
                $parent_page = Page::where('id', $this->version->parent_page_id)->withOnly([])->first()->setAppends([]);

                if ($parent_page instanceof Page) {
                    while ($parent_page->id > 1) {
                        if ($parent_page->hasViewPermissions()) {
                            $has_view_permissions = true;
                        }
                        $parent_page = $parent_page->parentPage;
                        if (!$parent_page) {
                            break;
                        }
                    }
                }
            }

            return $has_view_permissions;
        });
    }

    public function getBreadcrumbsAttribute()
    {
        return $this->getBreadcrumbs();
    }

    public function getBreadcrumbs()
    {
        $pages = collect();

        if ($this->version) {
            $pages->push($this->getBreadcrumb());

            $parent_page_id = $this->version->parent_page_id;

            while ($parent_page_id > 1) {
                $parent_page = Page::find($parent_page_id);
                if ($parent_page->version) {
                    $pages->push($parent_page->getBreadcrumb());
                    $parent_page_id = $parent_page->version->parent_page_id;
                } else {
                    break;
                }
            }
        }

        return $pages->filter()->reverse()->values();
    }

    public function getBreadcrumb()
    {
        return new Breadcrumb($this->full_slug, $this->version->title ?? $this->version->name);
    }

    public function getPreviousPageAttribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-previous-page', function () {
            if ($this->version->parent_page_id === 0) {
                return null;
            }

            return $this->parentPage->sub_menu->reverse()->values()->firstWhere(function ($sub_page) {
                return $sub_page->version->sort_order < $this->version->sort_order;
            })?->append(['full_slug', 'photo_link']);
        });
    }

    public function getPreviousPage2Attribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-previous-page2', function () {
            if ($this->version->parent_page_id === 0) {
                return null;
            }

            if (!$this->previous_page) {
                return null;
            }

            return $this->parentPage->sub_menu->reverse()->values()->firstWhere(function ($sub_page) {
                return $sub_page->version->sort_order < $this->previous_page->version->sort_order;
            })?->append(['full_slug', 'photo_link']);
        });
    }

    public function getNextPageAttribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-next-page', function () {
            if ($this->version->parent_page_id === 0) {
                return null;
            }

            return $this->parentPage->sub_menu->firstWhere(function ($sub_page) {
                return $sub_page->version->sort_order > $this->version->sort_order;
            })?->append(['full_slug', 'photo_link']);
        });
    }

    public function getNextPage2Attribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-next-page2', function () {
            if ($this->version->parent_page_id === 0) {
                return null;
            }

            if (!$this->next_page) {
                return null;
            }

            return $this->parentPage->sub_menu->firstWhere(function ($sub_page) {
                return $sub_page->version->sort_order > $this->next_page->version->sort_order;
            })?->append(['full_slug', 'photo_link']);
        });
    }
}
