<?php

declare(strict_types=1);

namespace App\Services;

use App\Events\Achievements\AchievementCheckRequested;
use App\Models\Story;
use App\Models\StoryBookmark;
use App\Models\StoryComment;
use App\Models\StoryLike;
use App\Models\User;
use App\Notifications\StoryCommentedNotification;
use App\Notifications\StoryLikedNotification;
use App\Notifications\StoryMentionedNotification;
use App\Services\ContentSanitizer;
use App\Support\AvatarUrl;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;

final class SocialService
{
    private const COMMENT_MAX_LENGTH = 10000;

    public function __construct(
        private readonly \App\Services\ActivityService $activity,
        private readonly FollowService $followService,
        private readonly XPService $xp,
    ) {}

    public function toggleFollow(int $actorId, int $targetId, bool $state): array
    {
        if ($state) {
            $this->followService->follow($actorId, $targetId);
        } else {
            $this->followService->unfollow($actorId, $targetId);
        }

        return [
            'following' => $state,
            'followers_count' => $this->followService->followersCount($targetId),
        ];
    }

    public function toggleStoryLike(User $actor, Story $story, bool $state): array
    {
        $changed = false;

        if ($state) {
            $like = StoryLike::query()->firstOrCreate([
                'story_id' => (int) $story->id,
                'user_id' => (int) $actor->id,
            ]);
            $changed = $like->wasRecentlyCreated;
        } else {
            $changed = StoryLike::query()
                ->where('story_id', $story->id)
                ->where('user_id', $actor->id)
                ->delete() > 0;
        }

        $likesCount = StoryLike::query()->where('story_id', $story->id)->count();
        $story->forceFill(['likes_count' => $likesCount])->save();

        if ($state && $changed) {
            $this->activity->record((int) $actor->id, 'story_like', 'story', (int) $story->id);

            if ((int) $story->creator_id > 0 && (int) $story->creator_id !== (int) $actor->id) {
                $creator = User::query()->find($story->creator_id);
                if ($creator) {
                    $creator->notify(new StoryLikedNotification($story, $actor));
                    event(new AchievementCheckRequested((int) $creator->id));
                }
            }
        }

        return [
            'ok' => true,
            'liked' => StoryLike::query()->where('story_id', $story->id)->where('user_id', $actor->id)->exists(),
            'likes_count' => $likesCount,
        ];
    }

    public function toggleStoryBookmark(User $actor, Story $story, bool $state): array
    {
        if ($state) {
            StoryBookmark::query()->firstOrCreate([
                'story_id' => (int) $story->id,
                'user_id' => (int) $actor->id,
            ]);
        } else {
            StoryBookmark::query()
                ->where('story_id', $story->id)
                ->where('user_id', $actor->id)
                ->delete();
        }

        return [
            'ok' => true,
            'bookmarked' => StoryBookmark::query()->where('story_id', $story->id)->where('user_id', $actor->id)->exists(),
            'bookmarks_count' => StoryBookmark::query()->where('story_id', $story->id)->count(),
        ];
    }

    public function listStoryComments(Story $story, ?int $viewerId, int $page = 1, int $perPage = 20): array
    {
        $comments = StoryComment::query()
            ->with(['user.profile', 'approvedReplies'])
            ->where('story_id', $story->id)
            ->where('is_approved', true)
            ->whereNull('parent_id')
            ->whereNull('deleted_at')
            ->latest('created_at')
            ->paginate($perPage, ['*'], 'page', max(1, $page));

        return [
            'data' => $comments->getCollection()->map(fn (StoryComment $comment) => $this->formatComment($comment, $viewerId, true))->values()->all(),
            'meta' => [
                'current_page' => $comments->currentPage(),
                'last_page' => $comments->lastPage(),
                'total' => $comments->total(),
                'per_page' => $comments->perPage(),
            ],
        ];
    }

    public function addStoryComment(User $actor, Story $story, string $raw, ?int $parentId = null): StoryComment
    {
        $trimmed = trim($raw);
        if ($trimmed === '' || mb_strlen($trimmed) > self::COMMENT_MAX_LENGTH) {
            abort(422, 'Invalid comment content.');
        }

        $errors = ContentSanitizer::validate($trimmed);
        if ($errors) {
            abort(422, implode(' ', $errors));
        }

        $parent = null;
        if ($parentId !== null) {
            $parent = StoryComment::query()
                ->where('story_id', $story->id)
                ->where('id', $parentId)
                ->where('is_approved', true)
                ->whereNull('deleted_at')
                ->first();

            if (! $parent) {
                abort(422, 'The comment you are replying to is no longer available.');
            }
        }

        $comment = DB::transaction(function () use ($actor, $story, $trimmed, $parent): StoryComment {
            $comment = StoryComment::query()->create([
                'story_id' => (int) $story->id,
                'user_id' => (int) $actor->id,
                'parent_id' => $parent?->id,
                'content' => $trimmed,
                'raw_content' => $trimmed,
                'rendered_content' => ContentSanitizer::render($trimmed),
                'is_approved' => true,
            ]);

            $commentsCount = StoryComment::query()
                ->where('story_id', $story->id)
                ->whereNull('deleted_at')
                ->count();

            $story->forceFill(['comments_count' => $commentsCount])->save();

            return $comment;
        });

        $comment->load(['user.profile', 'approvedReplies']);

        $this->activity->record((int) $actor->id, 'story_comment', 'story', (int) $story->id, ['comment_id' => (int) $comment->id]);
        $this->xp->awardCommentCreated((int) $actor->id, (int) $comment->id, 'story');

        $this->notifyStoryCommentRecipients($story, $comment, $actor, $parent);

        return $comment;
    }

    public function deleteStoryComment(User $actor, StoryComment $comment): void
    {
        $story = $comment->story;
        $canDelete = (int) $comment->user_id === (int) $actor->id
            || (int) ($story?->creator_id ?? 0) === (int) $actor->id
            || $actor->hasRole('admin')
            || $actor->hasRole('moderator');

        abort_unless($canDelete, 403);

        $comment->delete();

        if ($story) {
            $commentsCount = StoryComment::query()
                ->where('story_id', $story->id)
                ->whereNull('deleted_at')
                ->count();

            $story->forceFill(['comments_count' => $commentsCount])->save();
        }
    }

    public function storyStateFor(?User $viewer, Story $story): array
    {
        if (! $viewer) {
            return [
                'liked' => false,
                'bookmarked' => false,
                'is_following_creator' => false,
                'likes_count' => (int) $story->likes_count,
                'comments_count' => (int) $story->comments_count,
                'bookmarks_count' => StoryBookmark::query()->where('story_id', $story->id)->count(),
            ];
        }

        return [
            'liked' => StoryLike::query()->where('story_id', $story->id)->where('user_id', $viewer->id)->exists(),
            'bookmarked' => StoryBookmark::query()->where('story_id', $story->id)->where('user_id', $viewer->id)->exists(),
            'is_following_creator' => $story->creator_id ? $this->followService->isFollowing((int) $viewer->id, (int) $story->creator_id) : false,
            'likes_count' => StoryLike::query()->where('story_id', $story->id)->count(),
            'comments_count' => StoryComment::query()->where('story_id', $story->id)->whereNull('deleted_at')->count(),
            'bookmarks_count' => StoryBookmark::query()->where('story_id', $story->id)->count(),
        ];
    }

    public function formatComment(StoryComment $comment, ?int $viewerId, bool $includeReplies = false): array
    {
        $user = $comment->user;
        $avatarHash = $user?->profile?->avatar_hash;

        return [
            'id' => (int) $comment->id,
            'parent_id' => $comment->parent_id,
            'raw_content' => $comment->raw_content ?? $comment->content,
            'rendered_content' => $comment->rendered_content,
            'created_at' => $comment->created_at?->toIso8601String(),
            'time_ago' => $comment->created_at ? Carbon::parse($comment->created_at)->diffForHumans() : null,
            'can_delete' => $viewerId !== null && ((int) $comment->user_id === $viewerId || (int) ($comment->story?->creator_id ?? 0) === $viewerId),
            'user' => [
                'id' => (int) ($user?->id ?? 0),
                'username' => $user?->username,
                'display' => $user?->username ?? $user?->name ?? 'User',
                'profile_url' => $user?->username ? '/@' . $user->username : null,
                'avatar_url' => AvatarUrl::forUser((int) ($user?->id ?? 0), $avatarHash, 64),
                'level' => (int) ($user?->level ?? 1),
                'rank' => (string) ($user?->rank ?? 'Newbie'),
            ],
            'replies' => $includeReplies && $comment->relationLoaded('approvedReplies')
                ? $comment->approvedReplies->map(fn (StoryComment $reply) => $this->formatComment($reply, $viewerId, true))->values()->all()
                : [],
        ];
    }

    private function notifyStoryCommentRecipients(Story $story, StoryComment $comment, User $actor, ?StoryComment $parent): void
    {
        $notifiedUserIds = [];

        if ((int) ($story->creator_id ?? 0) > 0 && (int) $story->creator_id !== (int) $actor->id) {
            $creator = User::query()->find($story->creator_id);
            if ($creator) {
                $creator->notify(new StoryCommentedNotification($story, $comment, $actor));
                $notifiedUserIds[] = (int) $creator->id;
            }
        }

        if ($parent && (int) $parent->user_id !== (int) $actor->id && ! in_array((int) $parent->user_id, $notifiedUserIds, true)) {
            $parentUser = User::query()->find($parent->user_id);
            if ($parentUser) {
                $parentUser->notify(new StoryCommentedNotification($story, $comment, $actor));
                $notifiedUserIds[] = (int) $parentUser->id;
            }
        }

        $mentionedUsers = User::query()
            ->whereIn(DB::raw('LOWER(username)'), $this->extractMentions((string) ($comment->raw_content ?? '')))
            ->get();

        foreach ($mentionedUsers as $mentionedUser) {
            if ((int) $mentionedUser->id === (int) $actor->id || in_array((int) $mentionedUser->id, $notifiedUserIds, true)) {
                continue;
            }

            $mentionedUser->notify(new StoryMentionedNotification($story, $comment, $actor));
        }
    }

    private function extractMentions(string $content): array
    {
        preg_match_all('/(^|[^A-Za-z0-9_])@([A-Za-z0-9_-]{3,20})/', $content, $matches);

        return collect($matches[2] ?? [])
            ->map(fn ($username) => strtolower((string) $username))
            ->unique()
            ->values()
            ->all();
    }
}