<?php

namespace App\Traits;

use App\Traits\SoftDeletesControllerTrait;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;

use Illuminate\Support\Str;

use App\Utilities\PageResponse;
use App\Utilities\PageLink;
use App\Models\Page;
use App\Models\Blog;
use App\Models\Announcement;
use App\Models\User;
use App\Models\Role;
use App\Models\PageRedirect;

use App\Events\PageSaved;

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

trait PagesControllerTrait
{
    use SoftDeletesControllerTrait;

    abstract protected function getModel();
    abstract protected function getValidation();
    abstract protected function findPage($page);

    /**
     * Find the page associated with the requested URL path
     * This is the main function to render all content pages
     */
    public function load($id = null)
    {
        if (editing() && is_numeric($id)) {
            $page = $this->getModel()->find($id);
            if ($page) {
                if (!auth()->user()?->can('update', $page)) {
                    abort(404);
                }
            }
        }

        // find the page from the url
        if (!isset($page)) {
            $path = Str::lower(request()->path());
            $page = $this->findPage($path);
        }

        // If we have found a page display it
        if ($page) {
            return (new PageResponse())->view($page, 'pages.view');
        }

        // check for page redirect entry
        $page_redirect = PageRedirect::orderBy('sort_order')->get()->first(function ($page_redirect) use ($path) {
            $match = $page_redirect->checkRedirect($path);
            return $match ? true : false;
        });

        if ($page_redirect) {
            return redirect($page_redirect->processRedirect($path), 301);
        }

        // see if there is a parent fragment

        $parent = preg_replace("/(.*)\/([^\/]+)\/?$/", "$1", $path);

        $parents = $this->getModel()->with('contentElements', 'publishedVersion', 'versions')->get()->filter(function ($page) use ($parent) {
            return $page->full_slug === $parent;
        });

        if ($parents->count()) {
            return (new PageResponse())->view($parents->last(), 'pages.view');
        }

        if (!editing()) {
            //Log::info('404: '.$path);
        }

        abort(404);
    }

    /**
     * Save a page and all its stuff
     * Look in App\Page for the actual save function
     */
    public function store($id = null)
    {
        if ($id) {
            /**
             * here we check the policy file, App\Policies\PagePolicy or App\Policies\BlogPolicy
             * https://laravel.com/docs/master/authorization#creating-policies
             */
            if (!auth()->user()->can('update', $this->getModel()->findOrFail($id))) {
                if (request()->expectsJson()) {
                    return response()->json(['error' => 'You do not have permission to update that page'], 403);
                }
                return redirect('/')->with(['error' => 'You do not have permission to update that page']);
            }
        } else {
            if (!$this->userCanCreate(request()->all())) {
                if (request()->expectsJson()) {
                    return response()->json(['error' => 'You do not have permission to create that page'], 403);
                }
                return redirect('/')->with(['error' => 'You do not have permission to create that page']);
            }
        }

        Validator::make(request()->all(), $this->getValidation()->rules((int) $id))->validate();

        // we do this to auto turn on editing after creating a new blog
        if (!editing()) {
            auth()->user()->enableEditing();
        }

        $saveFunction = 'save'.class_basename($this->getModel());
        $page = ($this->getModel())->$saveFunction(requestInput(), $id);

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

        $page->refresh();

        $success_message = $page->version->name.' Saved';

        return (new PageResponse())->view($page, 'pages.edit', ['success' => $success_message]);
    }

    public function userCanCreate($input)
    {
        return auth()->user()->can('create', $this->getModel());
    }

    public function publish($id)
    {
        if (!auth()->check()) {
            if (request()->expectsJson()) {
                return response()->json(['error' => 'You do not have permission to publish that page'], 403);
            }
            return redirect('/')->with(['error' => 'You do not have permission to publish that page']);
        }

        $page = $this->getModel()->findOrFail($id);

        if (!auth()->user()->can('publish', $page)) {
            if (request()->expectsJson()) {
                return response()->json(['error' => 'You do not have permission to publish that page'], 403);
            }
            return redirect('/')->with(['error' => 'You do not have permission to publish that page']);
        }

        //$page->load('contentElements', 'contentElements.contentables');
        $page->publish();
        $page->flushCache();

        if ($page->type === 'page') {
            cache()->tags(['nav-menu'])->flush();
        }

        return (new PageResponse())->view($page, 'pages.edit', ['success' => Str::title(class_basename($page)).' Published']);
    }

    public function publishingRequest($id)
    {
        if (!auth()->check()) {
            if (request()->expectsJson()) {
                return response()->json(['error' => 'You do not have permission to request publishing.'], 403);
            }
            return redirect('/')->with(['error' => 'You do not have permission to request publishing.']);
        }

        $page = $this->getModel()->findOrFail($id);

        if (!auth()->user()->can('update', $page)) {
            if (request()->expectsJson()) {
                return response()->json(['error' => 'You do not have permission to request publishing.'], 403);
            }
            return redirect('/')->with(['error' => 'You do not have permission to request publishing.']);
        }

        if ($page instanceof Blog) {
            $publishers = User::whereHas('roles', function ($query) {
                $query->where('name', 'blogs-publisher');
            })->get();
        } elseif ($page instanceof Announcement) {
            $publishers = User::whereHas('roles', function ($query) {
                $query->where('name', 'announcements-publisher');
            })->get();
        } else {
            $publishers = User::whereHas('permissions', function ($query) {
                $query->where('action', 'publish');
            })->get();

            $publisher_roles = Role::whereHas('permissions', function ($query) {
                $query->where('action', 'publish');
            })
            ->get()
            ->map(function ($role) {
                return $role->users;
            })->flatten();

            $publishers = $publishers->merge($publisher_roles);
        }

        $publishers = $publishers->filter(function ($user) use ($page) {
            return $user->can('publish', $page);
        });

        $url = url('/').'/'.$page->full_slug.'?editing=true';
        $title = $page->version->title ?? $page->version->name;

        foreach ($publishers as $user) {
            Mail::to($user->email)
                ->queue(new PublishingRequest(auth()->user(), $title, $url));
        }

        $version = $page->getDraftVersion();
        $version->publishing_requested_at = now();
        $version->publishing_requested_user_id = auth()->user()->id;
        $version->save();

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

        $version->refresh();
        $page->refresh();

        if ($page instanceof Page) {
            broadcast(new PageSaved($page))->toOthers();
        }

        return (new PageResponse())->view($page, 'pages.edit', ['success' => 'Publishing Requested']);
    }

    /*
    public function remove($id)
    {
        $page = $this->getModel()->findOrFail($id);

        if (!auth()->check()) {
            return abort(401);
        }

        if ($page->slug === '/') {
            if (request()->expectsJson()) {
                return response()->json(['error' => 'The home page cannot be deleted'], 403);
            }
            return redirect('/')->with(['error' => 'The home page cannot be deleted']);
        }

        if (!auth()->user()->can('delete', $page)) {
            if (request()->expectsJson()) {
                return response()->json(['error' => 'You do not have permission to remove that page'], 403);
            }
            return redirect('/')->with(['error' => 'You do not have permission to remove that page']);
        }

        $page->delete();

        return response()->json(['success' => 'Page Removed']);

    }
     */

    public function unlist($id)
    {
        if (!auth()->check()) {
            if (request()->expectsJson()) {
                return response()->json(['error' => 'You do not have permission to hide that page'], 403);
            }
            return redirect('/')->with(['error' => 'You do not have permission to hide that page']);
        }

        $page = $this->getModel()->findOrFail($id);

        if (!auth()->user()->can('update', $page)) {
            if (request()->expectsJson()) {
                return response()->json(['error' => 'You do not have permission to hide that page'], 403);
            }
            return redirect('/')->with(['error' => 'You do not have permission to hide that page']);
        }

        $version = $page->getDraftVersion();
        $version->unlisted = 1;
        $version->save();
        $version->refresh();
        $page->refresh();

        if (request('publish') && auth()->user()->can('publish', $page)) {
            $page->publish();
            $version->refresh();
            $page->refresh();
        }

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

        return (new PageResponse())->view($page, 'pages.edit', ['success' => Str::title(class_basename($page)).' Hidden']);
    }

    public function reveal($id)
    {
        if (!auth()->check()) {
            if (request()->expectsJson()) {
                return response()->json(['error' => 'You do not have permission to unhide that page'], 403);
            }
            return redirect('/')->with(['error' => 'You do not have permission to unhide that page']);
        }

        $page = $this->getModel()->findOrFail($id);

        if (!auth()->user()->can('update', $page)) {
            if (request()->expectsJson()) {
                return response()->json(['error' => 'You do not have permission to unhide that page'], 403);
            }
            return redirect('/')->with(['error' => 'You do not have permission to unhide that page']);
        }

        $version = $page->getDraftVersion();
        $version->unlisted = 0;
        $version->save();
        $version->refresh();
        $page->refresh();

        if (request('publish') && auth()->user()->can('publish', $page)) {
            $page->publish();
            $version->refresh();
            $page->refresh();
        }

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

        return (new PageResponse())->view($page, 'pages.edit', ['success' => Str::title(class_basename($page)).' Revealed']);
    }

    public function attributes($id)
    {
        if (!auth()->check()) {
            abort(401);
        }

        $page = $this->getModel()->findOrFail($id);

        if (!auth()->user()->can('update', $page)) {
            return response()->json(['error' => 'You do not have permission to load attributes'], 403);
        }

        Validator::make(request()->all(), [
            'attributes' => 'required|array',
        ])->validate();

        foreach (request('attributes') as $attribute) {
            $page->append($attribute);
        }

        return response()->json([
            'page' => $page,
        ]);
    }

    public function signedUrl($id)
    {
        if (!auth()->check()) {
            abort(401);
        }

        $page = $this->getModel()->findOrFail($id);

        if (!auth()->user()->can('update', $page)) {
            return response()->json(['error' => 'You do not have permission to load attributes'], 403);
        }

        return response()->json([ 'signed_url' => $page->signed_url ]);
    }

    public function previewUrl($id)
    {
        if (!auth()->check()) {
            abort(401);
        }

        $page = $this->getModel()->findOrFail($id);

        if (!auth()->user()->can('update', $page)) {
            return response()->json(['error' => 'You do not have permission to load attributes'], 403);
        }

        return response()->json([ 'preview_url' => $page->preview_url ]);
    }

    public function updatePublishedAt($id)
    {
        if (!auth()->check()) {
            abort(401);
        }

        $page = $this->getModel()->findOrFail($id);

        if (!auth()->user()->can('update', $page)) {
            return response()->json(['error' => 'You do not have permission to update published at'], 403);
        }

        Validator::make(request()->all(), [
            'published_at' => 'required|date',
        ])->validate();

        $page->updateFirstPublishedAt(request('published_at'));

        return response()->json([
            'page' => $page,
            'success' => 'Published At Updated',
        ]);
    }
}
