<?php

namespace App\Http\Controllers\Gallery;

use App\Album;
use App\Facade\Theme;
use App\Facade\UserConfig;
use App\Helpers\DbHelper;
use App\Helpers\PermissionsHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\StorePhotoCommentRequest;
use App\Mail\ModeratePhotoComment;
use App\Mail\PhotoCommentApproved;
use App\Mail\PhotoCommentApprovedUser;
use App\Mail\PhotoCommentRepliedTo;
use App\Permission;
use App\Photo;
use App\PhotoComment;
use App\User;
use App\UserActivity;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\ValidationException;

class PhotoCommentController extends Controller
{
    public function moderate(Request $request, $albumUrlAlias, $photoFilename, $commentID)
    {
        $album = null;

        /** @var Photo $photo */
        $photo = null;

        /** @var PhotoComment $comment */
        $comment = null;

        if (!$this->loadAlbumPhotoComment($albumUrlAlias, $photoFilename, $commentID, $album, $photo, $comment))
        {
            return null;
        }

        if (!User::currentOrAnonymous()->can('moderate-comments', $photo))
        {
            App::abort(403);
            return null;
        }

        if (!$comment->isModerated())
        {
            if ($request->has('approve'))
            {
                $comment->approved_at = new \DateTime();
                $comment->approved_user_id = $this->getUser()->id;
                $comment->save();

                $this->createUserActivityRecord($comment);
                $this->notifyAlbumOwnerAndPoster($album, $photo, $comment);
                $request->getSession()->flash('success', trans('gallery.photo_comment_approved_successfully'));
            }
            else if ($request->has('reject'))
            {
                $comment->rejected_at = new \DateTime();
                $comment->rejected_user_id = $this->getUser()->id;
                $comment->save();

                $request->getSession()->flash('success', trans('gallery.photo_comment_rejected_successfully'));
            }
        }

        return redirect($photo->url());
    }

    public function reply(Request $request, $albumUrlAlias, $photoFilename, $commentID)
    {
        $album = null;

        /** @var Photo $photo */
        $photo = null;

        /** @var PhotoComment $comment */
        $comment = null;

        if (!$this->loadAlbumPhotoComment($albumUrlAlias, $photoFilename, $commentID, $album, $photo, $comment))
        {
            return null;
        }

        if (!User::currentOrAnonymous()->can('post-comment', $photo))
        {
            App::abort(403);
            return null;
        }

        return Theme::render('partials.photo_comments_reply_form', [
            'photo' => $photo,
            'reply_comment' => $comment
        ]);
    }

    public function store(Request $request, $albumUrlAlias, $photoFilename)
    {
        $album = null;

        /** @var Photo $photo */
        $photo = null;

        /** @var PhotoComment $comment */
        $comment = null;

        if (!$this->loadAlbumPhotoComment($albumUrlAlias, $photoFilename, 0, $album, $photo, $comment))
        {
            return null;
        }

        if (!User::currentOrAnonymous()->can('post-comment', $photo))
        {
            App::abort(403);
            return null;
        }

        // Validate and link the parent comment, if provided
        // We do this here so if the validation fails, we still have the parent comment available in the catch block
        $parentComment = null;
        if ($request->has('parent_comment_id'))
        {
            $parentComment = $photo->comments()->where('id', intval($request->get('parent_comment_id')))->first();

            if (is_null($parentComment))
            {
                return redirect($photo->url());
            }
        }

        try
        {
            $this->validate($request, [
                'name' => 'required|max:255',
                'email' => 'sometimes|max:255|email',
                'comment' => 'required'
            ]);

            $commentText = $this->stripDisallowedHtmlTags($request->get('comment'));

            $comment = new PhotoComment();
            $comment->photo_id = $photo->id;
            $comment->fill($request->only(['name', 'email']));
            $comment->comment = $commentText;

            if (!is_null($parentComment))
            {
                $comment->parent_comment_id = $parentComment->id;
            }

            // Set the created user ID if we're logged in
            $user = $this->getUser();
            if (!is_null($user) && !$user->isAnonymous())
            {
                $comment->created_user_id = $user->id;
            }

            // Auto-approve the comment if we're allowed to moderate comments
            $isAutoApproved = false;
            if (User::currentOrAnonymous()->can('moderate-comments', $photo))
            {
                $comment->approved_at = new \DateTime();
                $comment->approved_user_id = $user->id;
                $isAutoApproved = true;
            }

            // Auto-approve the comment if settings allow
            if ($user->isAnonymous() && !UserConfig::get('moderate_anonymous_users'))
            {
                $comment->approved_at = new \DateTime();
                $comment->approved_user_id = null; // we don't have a user ID to set!
                $isAutoApproved = true;
            }
            else if (!$user->isAnonymous() && !UserConfig::get('moderate_known_users'))
            {
                $comment->approved_at = new \DateTime();
                $comment->approved_user_id = $user->id;
                $isAutoApproved = true;
            }

            $comment->save();

            // Send notification e-mails to moderators or album owner
            if (!$isAutoApproved)
            {
                $this->notifyAlbumModerators($album, $photo, $comment);
                $request->getSession()->flash('success', trans('gallery.photo_comment_posted_successfully_pending_moderation'));
            }
            else
            {
                // Log an activity record for the user's feed
                $this->createUserActivityRecord($comment);
                $this->notifyAlbumOwnerAndPoster($album, $photo, $comment);
                $request->getSession()->flash('success', trans('gallery.photo_comment_posted_successfully'));
            }

            if ($request->isXmlHttpRequest())
            {
                return response()->json(['redirect_url' => $photo->url()]);
            }
            else
            {
                return redirect($photo->url());
            }
        }
        catch (ValidationException $e)
        {
            if (!is_null($parentComment))
            {
                return redirect()
                    ->to($photo->replyToCommentFormUrl($parentComment->id))
                    ->withErrors($e->errors())
                    ->withInput($request->all());
            }
            else
            {
                return redirect()
                    ->back()
                    ->withErrors($e->errors())
                    ->withInput($request->all());
            }
        }
    }

    private function createUserActivityRecord(PhotoComment $comment)
    {
        if (!is_null($comment->created_user_id))
        {
            $userActivity = new UserActivity();
            $userActivity->user_id = $comment->created_user_id;
            $userActivity->activity_at = $comment->created_at;

            if (is_null($comment->parent_comment_id))
            {
                $userActivity->type = 'photo.commented';
            }
            else
            {
                $userActivity->type = 'photo.comment_replied';
            }

            $userActivity->photo_id = $comment->photo_id;
            $userActivity->photo_comment_id = $comment->id;
            $userActivity->save();
        }
    }

    private function loadAlbumPhotoComment($albumUrlAlias, $photoFilename, $commentID, &$album, &$photo, &$comment)
    {
        $album = DbHelper::getAlbumByPath($albumUrlAlias);
        if (is_null($album))
        {
            App::abort(404);
            return false;
        }

        $this->authorizeForUser($this->getUser(), 'view', $album);

        $photo = PhotoController::loadPhotoByAlbumAndFilename($album, $photoFilename);

        if (!UserConfig::get('allow_photo_comments'))
        {
            // Not allowed to post comments
            App::abort(404);
            return false;
        }

        if (intval($commentID > 0))
        {
            $comment = $photo->comments()->where('id', $commentID)->first();
            if (is_null($comment))
            {
                App::abort(404);
                return false;
            }
        }

        return true;
    }

    /**
     * Loads a given comment by its ID.
     * @param $id
     * @return PhotoComment
     */
    private function loadCommentByID($id)
    {
        $comment = PhotoComment::where('id', intval($id))->first();
        if (is_null($comment))
        {
            App::abort(404);
        }

        return $comment;
    }

    /**
     * Sends an e-mail notification to an album's moderators that a comment is available to moderate.
     * @param Album $album
     * @param Photo $photo
     * @param PhotoComment $comment
     */
    private function notifyAlbumModerators(Album $album, Photo $photo, PhotoComment $comment)
    {
        // Get all users from the cache
        $helper = new PermissionsHelper();
        $moderators = $helper->usersWhoCan_Album($album, 'moderate-comments');

        /** @var User $moderator */
        foreach ($moderators as $moderator)
        {
            Mail::to($moderator)->send(new ModeratePhotoComment($moderator, $album, $photo, $comment));
        }
    }

    /**
     * Sends an e-mail notification to an album's owned that a comment has been posted/approved.
     * @param Album $album
     * @param Photo $photo
     * @param PhotoComment $comment
     */
    private function notifyAlbumOwnerAndPoster(Album $album, Photo $photo, PhotoComment $comment)
    {
        $owner = $album->user;

        Mail::to($owner)->send(new PhotoCommentApproved($owner, $album, $photo, $comment));

        // Also send a notification to the comment poster
        $poster = new User();
        $poster->name = $comment->authorDisplayName();
        $poster->email = $comment->authorEmail();

        Mail::to($poster)->send(new PhotoCommentApprovedUser($poster, $album, $photo, $comment));

        // Send notification to the parent comment owner (if this is a reply)
        if (!is_null($comment->parent_comment_id))
        {
            $parentComment = $this->loadCommentByID($comment->parent_comment_id);

            if (is_null($parentComment))
            {
                return;
            }

            $parentPoster = new User();
            $parentPoster->name = $parentComment->authorDisplayName();
            $parentPoster->email = $parentComment->authorEmail();

            Mail::to($parentPoster)->send(new PhotoCommentRepliedTo($parentPoster, $album, $photo, $comment));
        }
    }

    private function stripDisallowedHtmlTags($commentText)
    {
        $allowedHtmlTags = explode(',', UserConfig::get('photo_comments_allowed_html'));
        $allowedHtmlTagsCleaned = [];

        foreach ($allowedHtmlTags as $tag)
        {
            $allowedHtmlTagsCleaned[] = trim($tag);
        }

        // Match any starting HTML tags
        $regexMatchString = '/<(?!\/)([a-z]+)(?:\s.*)*>/Us';

        $htmlTagMatches = [];
        preg_match_all($regexMatchString, $commentText, $htmlTagMatches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);

        for ($index = 0; $index < count($htmlTagMatches); $index++)
        {
            $htmlTagMatch = $htmlTagMatches[$index];

            $htmlTag = $htmlTagMatch[1][0]; // e.g. "p" for <p>
            if (in_array($htmlTag, $allowedHtmlTagsCleaned))
            {
                // This tag is allowed - carry on
                continue;
            }

            /* This tag is not allowed - remove it from the string */

            // Find the closing tag
            $disallowedStringOffset = $htmlTagMatch[0][1];
            $endingTagMatches = [];
            preg_match(sprintf('/(<%1$s.*>)(.+)<\/%1$s>/Us', $htmlTag), $commentText, $endingTagMatches, 0, $disallowedStringOffset);

            // Replace the matched string with the inner string
            $commentText = substr_replace($commentText, $endingTagMatches[2], $disallowedStringOffset, strlen($endingTagMatches[0]));

            // Adjust the offsets for strings after the one we're processing, so the offsets match up with the string correctly
            for ($index2 = $index + 1; $index2 < count($htmlTagMatches); $index2++)
            {
                // If this string appears entirely BEFORE the next one starts, we need to subtract the entire length.
                // Otherwise, we only need to substract the length of the start tag, as the next one starts within it.
                $differenceAfterReplacement = strlen($endingTagMatches[1]);

                if ($htmlTagMatch[0][1] + strlen($endingTagMatches[0]) < $htmlTagMatches[$index2][0][1])
                {
                    $differenceAfterReplacement = strlen($endingTagMatches[0]) - strlen($endingTagMatches[2]);
                }

                $htmlTagMatches[$index2][0][1] -= $differenceAfterReplacement;
                $htmlTagMatches[$index2][1][1] -= $differenceAfterReplacement;
            }
        }

        return $commentText;
    }
}