<?php

namespace App\Traits;

use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use Illuminate\Database\Eloquent\Relations\MorphOne;

use App\Models\Version;
use App\Models\ContentElement;
use App\Models\Page;
use Carbon\Carbon;

use Illuminate\Support\Facades\Mail;
use App\Mail\PublishingCompleted;

use App\Events\MenuUpdated;

trait VersioningTrait
{
    public function versions()
    {
        return $this->morphMany(Version::class, 'versionable');
    }

    public function publishedVersion()
    {
        return $this->belongsTo(Version::class, 'published_version_id');
    }

    public function getPublishedAtAttribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-published-at', function () {
            return $this->publishedVersion ? $this->publishedVersion->published_at : null;
        });
    }

    public function getFirstPublishedAtAttribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-first-published-at', function () {
            return $this->versions()->whereNotNull('published_at')->orderBy('id')->first()?->published_at;
        });
    }

    public function publishContentElement(ContentElement $content_element)
    {
        return $this->publish($content_element);
    }

    public function publish(ContentElement $publish_now_content_element = null)
    {
        if ($publish_now_content_element) {
            $publish_at_content_elements = collect([$publish_now_content_element]);
        } else {
            $publish_at_content_elements = $this->contentElements()
                                                ->where('publish_at', '<', now())
                                                ->get()
                                                ->filter(function ($content_element) {
                                                    return Version::find($content_element->pivot->version_id)->published_at ? false : true;
                                                });
        }

        $new_draft_content_elements = $this->contentElements()
                                        ->get()
                                        ->filter(function ($content_element) {
                                            return Version::find($content_element->pivot->version_id)->published_at ? false : true;
                                        })
                                        ->filter(function ($content_element) use ($publish_at_content_elements) {
                                            return !$publish_at_content_elements->contains('id', $content_element->id);
                                        });

        $publisher = auth()->check() ? auth()->user() : null;

        if ($publish_at_content_elements->count() && !$publisher) {
            $publisher = $publish_at_content_elements->map->publisher->filter()->first();
        }

        $draft_version = $this->getDraftVersion();
        $draft_version->publish($publisher);
        $this->published_version_id = $draft_version->id;
        //$this->publish_at = null;
        $this->save();

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

        if ($publish_at_content_elements->count()) {
            foreach ($new_draft_content_elements as $new_draft_content_element) {
                $contentable = $new_draft_content_element->contentables()
                                                          ->where('contentable_id', $this->id)
                                                          ->where('contentable_type', get_class($this))
                                                          ->first();
                $contentable->version_id = $this->getDraftVersion()->id;
                $contentable->save();
            }
        }

        $event_class = '\\App\\Events\\'.class_basename($this).'Saved';

        broadcast(new $event_class($this))->toOthers();
        if ($this->type === 'page') {
            broadcast(new MenuUpdated());
        }

        // find other instances of the content elements that were just published and update them as well
        if ($publish_at_content_elements->count()) {
            $search_content_elements = $publish_at_content_elements;
        } else {
            $search_content_elements = $this->contentElements()->get();
        }

        $uuids = $search_content_elements->map(function ($ce) {
            return $ce->uuid;
        })
        ->flatten()
        ->unique();

        foreach ($uuids as $uuid) {
            $pages = ContentElement::findPagesByUuid($uuid);

            if ($pages->count()) {
                foreach ($pages as $page) {
                    $ces = $page->contentElements()->with('contentables')->get()->filter(function ($ce) use ($uuid) {
                        return $ce->uuid === $uuid;
                    });

                    foreach ($ces as $content_element) {
                        if (!$content_element->getPageVersion($page)->published_at) {
                            $page->publishContentElement($content_element);
                        }
                    }
                }
            }
        }

        if ($this instanceof Page) {
            $this->clearSlugCache();
        }

        if ($draft_version->publishingRequestedUser) {
            $url = url('/').'/'.$this->full_slug;
            $title = $draft_version->title ?? $draft_version->name;

            Mail::to($draft_version->publishingRequestedUser->email)
                ->queue(new PublishingCompleted($title, $url, auth()->user()));
        }

        return $this->refresh();
    }

    public function saveVersion($input)
    {
        if (Arr::get($input, 'version')) {
            $version = $this->getDraftVersion();
            $version = (new Version())->saveVersion($this, Arr::get($input, 'version'), $version->id);
        }
        return $this;
    }

    public function getDraftVersion()
    {
        $draft_version = $this->versions()->whereNull('published_at')->get()->first();
        if ($draft_version) {
            return $draft_version;
        } else {
            $input = [
                'version_number' => $this->versions->count() + 1,
            ];

            $published_version = $this->publishedVersion()->first();

            if ($published_version) {
                $published_input = Arr::only($published_version->load('footerFgPhoto', 'footerBgPhoto')->toArray(), [
                    'name',
                    'title',
                    'slug',
                    'parent_page_id',
                    'theme',
                    'sort_order',
                    'unlisted',
                    'search_exclude',
                    'redirect',
                    'show_sub_menu',
                    'footer_color',
                    'footer_fg_photo',
                    'footer_bg_photo',
                    'description',
                    'signed_url',
                    'sibling_nav',
                    'randomize',
                ]);

                $input = array_merge($published_input, $input);

                /*
                if ($this->protected) {
                    $input['slug'] = $published_version->slug;
                    $input['parent_page_id'] = $published_version->parent_page_id;
                }
                 */
            }

            $version = (new Version())->saveVersion($this, $input, null);

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

            $event_class = '\\App\\Events\\'.class_basename($this).'DraftCreated';
            broadcast(new $event_class($this->load('versions')));

            return $version;
        }
    }

    public function getDraftVersionIdAttribute()
    {
        return $this->getDraftVersion()->id;
    }

    public function version(): MorphOne
    {
        return $this->morphOne(Version::class, 'versionable')->ofMany([
            'id' => 'max',
        ], function ($query) {
            if (!auth()->check() && !request('preview') && !request()->hasValidSignature()) {
                $query->whereNotNull('published_at');
            } else {
                if (editing() || (request('preview') && request()->hasValidSignature())) {
                    // take the lastest version, published or not
                } else {
                    $query->whereNotNull('published_at');
                }
            }
        });
    }

    /*
    public function getActiveVersion()
    {
        $published_version = cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-published-version', function () {
            return $this->publishedVersion?->load('footerFgPhoto', 'footerBgPhoto');
        });

        if (!auth()->check() && !request('preview') && !request()->hasValidSignature()) {
            return $published_version;
        }

        // We can't do a permissions check in here as it loops
        if (editing() || (request('preview') && request()->hasValidSignature())) {
            $draft_version = cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-version-draft', function () {
                //return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-draft-version', function() {
                return $this->versions->whereNull('published_at')->first()?->load('footerFgPhoto', 'footerBgPhoto', 'publishingRequestedUser');
                //});
            });
            return $draft_version ?? $published_version;
        } else {
            return $published_version;
        }
    }
     */

    /*
    public function getVersionAttribute()
    {
        return $this->getActiveVersion();
    }
     */

    public function getCanBePublishedAttribute()
    {
        if (!auth()->check()) {
            return false;
        }

        if (!auth()->user()->can('publish', $this)) {
            return false;
        }

        if (!$this->published_version_id && $this->contentElements->count()) {
            return true;
        }

        return $this->all_content_elements->filter(function ($content_element) {
            return Version::find($content_element->pivot->version_id)->published_at ? false : true;
        })->count() ? true : false;
    }

    public function getFooterTextColorAttribute()
    {
        return $this->version?->footer_text_color;
    }

    public function getFooterFgPhotoAttribute()
    {
        return $this->version?->getFooterFgPhoto();
    }

    public function getFooterBgPhotoAttribute()
    {
        return $this->version?->getFooterBgPhoto();
    }

    public function getFooterColorAttribute()
    {
        return $this->version?->getFooterColor();
    }

    public function getNameAttribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-name', function () {
            return $this->version?->name ?? $this->versions()->get()->last()->name;
        });
    }

    public function getSignedUrlAttribute()
    {
        if ($this->full_slug && $this->version?->signed_url) {
            return URL::signedRoute('pages.load', ['page' => $this->full_slug]);
        }
        return null;
    }

    public function getPreviewUrlAttribute()
    {
        //if (auth()->user()?->can('update', $this) || (request('preview') && request()->hasValidSignature())) {
        if (auth()->user()?->can('update', $this)) {
            return URL::signedRoute('pages.load', ['page' => $this->full_slug, 'preview' => 'true']);
        }
        return null;
    }

    public function flushCache()
    {
        cache()->tags([cache_name($this)])->flush();
    }

    public function updateFirstPublishedAt($date)
    {
        $date = Carbon::parse($date);

        $version = $this->versions()->whereNotNull('published_at')->orderBy('id')->first();

        if ($version) {
            $version->published_at = $date;
            $version->save();
        }

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

        return $this;
    }

    public function getVisibleSubMenuAttribute()
    {
        if ($this->type !== 'page') {
            return false;
        }

        if ($this->id > 1) {
            if ($this->parentPage?->version?->show_sub_menu) {
                if ($this->parentPage->sub_menu?->count()) {
                    return true;
                }
            }
        }
        return false;
    }

    public function parentPage()
    {
        if (!$this instanceof Page) {
            return null;
        }
        $query = $this->hasOneThrough(Page::class, Version::class, 'versionable_id', 'id', 'id', 'parent_page_id')
                    ->where('versions.versionable_type', get_class($this))
                    ->orderBy('versions.id', 'desc');
        if (!editing()) {
            $query->whereNotNull('versions.published_at');
        }
        return $query;
    }
}
