<?php

namespace App\Controllers\Web;

use App\Models\Comment;
use App\Models\Topic;
use App\Models\User;
use App\Services\Socket;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Psr\Http\Message\ResponseInterface;
use Vesp\Controllers\ModelController;

/**
 * @property User $user
 */
class Comments extends ModelController
{
    protected string|array $scope = 'comments';
    protected string $model = Comment::class;
    protected int $maxLimit = 0;
    private ?Topic $topic = null;
    private bool $isNew = false;
    private bool $isAdmin = false;
    private bool $isVip = false;
    private bool $hasAccess = true;

    public function checkScope(string $method): ?ResponseInterface
    {
        if (!in_array($method, ['options', 'get']) && $error = parent::checkScope($method)) {
            return $error;
        }

        $this->isAdmin = $this->user && $this->user->hasScope('comments/delete');
        $this->isVip = $this->user && $this->user->hasScope('vip');
        $uuid = $this->getProperty('topic_uuid');

        if (!$uuid || !$this->topic = $this->getTopic($uuid)) {
            return $this->failure('Not Found', 404);
        }
        $this->hasAccess = $this->topic->hasAccess($this->user);

        if (!$this->isAdmin) {
            $this->unsetProperty('active');
        }

        return null;
    }

    protected function getTopic(string $uuid): ?Topic
    {
        $c = Topic::query()->where('uuid', $uuid);
        if (!$this->isAdmin) {
            $c->where('active', true);
        }
        /** @var ?Topic $topic */
        $topic = $c->first();

        return $topic;
    }

    protected function beforeCount(Builder $c): Builder
    {
        $c->where('topic_id', !$this->topic->hide_comments ? $this->topic->id : -1);

        return $c;
    }

    protected function afterCount(Builder $c): Builder
    {
        $c->with('user', function (BelongsTo $c) {
            $c->select('id', 'username', 'fullname', 'role_id', 'avatar_id', 'blocked');
            $c->with('avatar:id,uuid,updated_at');
            $c->with('role:id,title,color');
            $c->with('currentSubscription:id,user_id,level_id', 'currentSubscription.level:id,title,color');
        });
        if ($this->user) {
            $c->with('userReactions', function (HasMany $c) {
                $c->where('user_id', $this->user->id);
            });
        }

        return $c;
    }

    protected function addSorting(Builder $c): Builder
    {
        return $c->orderBy('id');
    }

    protected function beforeSave(Model $record): ?ResponseInterface
    {
        if (!$this->user || !$this->hasAccess || !$this->user->hasScope('comments/put')) {
            return $this->failure('components.comments.info.no_scope');
        }
        if (!$this->isAdmin) {
            if ($this->topic->closed || $this->topic->hide_comments) {
                return $this->failure('components.comments.info.closed');
            }
            if ($this->user->isReadonly()) {
                return $this->failure('components.comments.info.readonly');
            }
            if (!$this->isVip && !$this->user->currentSubscription) {
                if ($this->topic->isFree() && getenv('COMMENTS_REQUIRE_SUBSCRIPTION')) {
                    return $this->failure('components.comments.info.no_subscription');
                }
            }
        }

        /** @var Comment $record */
        if (!$content = $record->content) {
            return $this->failure('errors.comment.no_content');
        }
        $content['blocks'] = !empty($content['blocks']) ? array_values($content['blocks']) : [];
        try {
            $record->content = $record::sanitizeContent($content);
        } catch (\Throwable $e) {
            return $this->failure('errors.comment.wrong_content');
        }

        if (!$this->isAdmin && $record->isDirty('active')) {
            return $this->failure('errors.no_scope');
        }

        if (!$record->exists) {
            $record->active = true;
            $record->parent_id = null;
            $record->topic_id = $this->topic->id;
            $record->user_id = $this->user->id;

            if ($parent_id = (int)$this->getProperty('parent_id')) {
                /** @var Comment $parent */
                if (!$parent = Comment::query()->where('active', true)->find($parent_id)) {
                    return $this->failure('errors.comment.wrong_parent');
                }
                if ($parent->topic_id !== $record->topic_id) {
                    return $this->failure('errors.comment.wrong_parent');
                }
                $record->parent_id = $parent->id;
            }
            $this->isNew = true;
        } elseif (!$this->isAdmin) {
            if ($this->user->id !== $record->user_id) {
                return $this->failure('errors.no_scope');
            }
            if ($record->children()->count()) {
                return $this->failure('errors.comment.children_exists');
            }
            if ($record->created_at->toImmutable()->addSeconds((int)getenv('COMMENTS_EDIT_TIME'))->timestamp < time()) {
                return $this->failure('errors.comment.edit_time');
            }
        }

        return null;
    }

    protected function afterSave(Model $record): Model
    {
        /** @var Comment $record */
        $record->processUploadedFiles();

        $record = $this->afterCount(Comment::query())->find($record->id);
        if ($this->isNew) {
            Socket::send('comment-create', $this->prepareRow($record));
            $this->createNotifications($record);
        } else {
            Socket::send('comment-update', $this->prepareRow($record));
        }

        Socket::send('topic-comments', [
            'id' => $record->topic->id,
            'comments_count' => $record->topic->comments_count,
        ]);

        return $record;
    }

    public function prepareRow(Model $object): array
    {
        /** @var Comment $object */
        return $object->prepareOutput($this->user);
    }

    protected function beforeDelete(Model $record): ?ResponseInterface
    {
        if (!$this->isAdmin) {
            return $this->failure('errors.no_scope');
        }

        return null;
    }

    public function delete(): ResponseInterface
    {
        $id = $this->getPrimaryKey();
        /** @var Comment $record */
        if (!$id || !$record = Comment::query()->find($id)) {
            return $this->failure('Could not find a record', 404);
        }

        $record->delete();
        Socket::send('comment-delete', $record->toArray());
        Socket::send('topic-comments', [
            'id' => $record->topic->id,
            'comments_count' => $record->topic->comments_count,
        ]);

        return $this->success();
    }

    protected function createNotifications(Comment $comment): void
    {
        $topic = $comment->topic;
        $parent = $comment->parent;

        if (getenv('COMMENTS_NOTIFY_REPLY')) {
            // Notify comment author
            if ($parent && $parent->user_id !== $comment->user_id && $parent->user->notify) {
                $parent->user->notifications()
                    ->create(['type' => 'comment-reply', 'topic_id' => $topic->id, 'comment_id' => $comment->id]);
            }
        }

        if (getenv('COMMENTS_NOTIFY_AUTHOR')) {
            // Notify topic author
            if ($topic->user_id !== $comment->user_id && $topic->user->notify && (!$parent || $parent->user_id !== $topic->user_id)) {
                $topic->user->notifications()
                    ->create(['type' => 'comment-new', 'topic_id' => $topic->id, 'comment_id' => $comment->id]);
            }
        }
    }
}