<?php

declare(strict_types=1);

namespace App\Services\Activity;

use App\Models\Achievement;
use App\Models\Artwork;
use App\Models\ArtworkComment;
use App\Models\ForumPost;
use App\Models\ForumThread;
use App\Models\User;
use App\Models\UserActivity;
use App\Support\AvatarUrl;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;

final class UserActivityService
{
    public const DEFAULT_PER_PAGE = 20;
    private const FEED_SCHEMA_VERSION = 2;

    private const FILTER_ALL = 'all';
    private const FILTER_UPLOADS = 'uploads';
    private const FILTER_COMMENTS = 'comments';
    private const FILTER_LIKES = 'likes';
    private const FILTER_FORUM = 'forum';
    private const FILTER_FOLLOWING = 'following';

    public function logUpload(int $userId, int $artworkId, array $meta = []): ?UserActivity
    {
        return $this->log($userId, UserActivity::TYPE_UPLOAD, UserActivity::ENTITY_ARTWORK, $artworkId, $meta);
    }

    public function logComment(int $userId, int $commentId, bool $isReply = false, array $meta = []): ?UserActivity
    {
        return $this->log(
            $userId,
            $isReply ? UserActivity::TYPE_REPLY : UserActivity::TYPE_COMMENT,
            UserActivity::ENTITY_ARTWORK_COMMENT,
            $commentId,
            $meta,
        );
    }

    public function logLike(int $userId, int $artworkId, array $meta = []): ?UserActivity
    {
        return $this->log($userId, UserActivity::TYPE_LIKE, UserActivity::ENTITY_ARTWORK, $artworkId, $meta);
    }

    public function logFavourite(int $userId, int $artworkId, array $meta = []): ?UserActivity
    {
        return $this->log($userId, UserActivity::TYPE_FAVOURITE, UserActivity::ENTITY_ARTWORK, $artworkId, $meta);
    }

    public function logFollow(int $userId, int $targetUserId, array $meta = []): ?UserActivity
    {
        return $this->log($userId, UserActivity::TYPE_FOLLOW, UserActivity::ENTITY_USER, $targetUserId, $meta);
    }

    public function logAchievement(int $userId, int $achievementId, array $meta = []): ?UserActivity
    {
        return $this->log($userId, UserActivity::TYPE_ACHIEVEMENT, UserActivity::ENTITY_ACHIEVEMENT, $achievementId, $meta);
    }

    public function logForumPost(int $userId, int $threadId, array $meta = []): ?UserActivity
    {
        return $this->log($userId, UserActivity::TYPE_FORUM_POST, UserActivity::ENTITY_FORUM_THREAD, $threadId, $meta);
    }

    public function logForumReply(int $userId, int $postId, array $meta = []): ?UserActivity
    {
        return $this->log($userId, UserActivity::TYPE_FORUM_REPLY, UserActivity::ENTITY_FORUM_POST, $postId, $meta);
    }

    public function feedForUser(User $user, string $filter = self::FILTER_ALL, int $page = 1, int $perPage = self::DEFAULT_PER_PAGE): array
    {
        $normalizedFilter = $this->normalizeFilter($filter);
        $resolvedPage = max(1, $page);
        $resolvedPerPage = max(1, min(50, $perPage));
        $version = $this->cacheVersion((int) $user->id);

        return Cache::remember(
            sprintf('user_activity_feed:%d:%d:%s:%d:%d', (int) $user->id, $version, $normalizedFilter, $resolvedPage, $resolvedPerPage),
            now()->addSeconds(30),
            function () use ($user, $normalizedFilter, $resolvedPage, $resolvedPerPage): array {
                return $this->buildFeed($user, $normalizedFilter, $resolvedPage, $resolvedPerPage);
            }
        );
    }

    public function normalizeFilter(string $filter): string
    {
        return match (strtolower(trim($filter))) {
            self::FILTER_UPLOADS => self::FILTER_UPLOADS,
            self::FILTER_COMMENTS => self::FILTER_COMMENTS,
            self::FILTER_LIKES => self::FILTER_LIKES,
            self::FILTER_FORUM => self::FILTER_FORUM,
            self::FILTER_FOLLOWING => self::FILTER_FOLLOWING,
            default => self::FILTER_ALL,
        };
    }

    public function invalidateUserFeed(int $userId): void
    {
        if ($userId <= 0) {
            return;
        }

        $this->bumpCacheVersion($userId);
    }

    private function log(int $userId, string $type, string $entityType, int $entityId, array $meta = []): ?UserActivity
    {
        if ($userId <= 0 || $entityId <= 0) {
            return null;
        }

        $activity = UserActivity::query()->create([
            'user_id' => $userId,
            'type' => $type,
            'entity_type' => $entityType,
            'entity_id' => $entityId,
            'meta' => $meta ?: null,
            'created_at' => now(),
        ]);

        $this->bumpCacheVersion($userId);

        return $activity;
    }

    private function buildFeed(User $user, string $filter, int $page, int $perPage): array
    {
        $query = UserActivity::query()
            ->where('user_id', (int) $user->id)
            ->whereNull('hidden_at')
            ->whereIn('type', $this->typesForFilter($filter))
            ->latest('created_at')
            ->latest('id');

        $total = (clone $query)->count();

        /** @var Collection<int, UserActivity> $rows */
        $rows = $query
            ->forPage($page, $perPage)
            ->get(['id', 'user_id', 'type', 'entity_type', 'entity_id', 'meta', 'created_at']);

        $actor = $user->loadMissing('profile')->loadCount('artworks');
        $related = $this->loadRelated($rows);

        $data = $rows
            ->map(fn (UserActivity $activity): ?array => $this->formatActivity($activity, $actor, $related))
            ->filter()
            ->values()
            ->all();

        return [
            'data' => $data,
            'meta' => [
                'current_page' => $page,
                'last_page' => (int) max(1, ceil($total / $perPage)),
                'per_page' => $perPage,
                'total' => $total,
                'has_more' => ($page * $perPage) < $total,
            ],
            'filter' => $filter,
        ];
    }

    private function loadRelated(Collection $rows): array
    {
        $artworkIds = $rows
            ->filter(fn (UserActivity $activity): bool => $activity->entity_type === UserActivity::ENTITY_ARTWORK)
            ->pluck('entity_id')
            ->map(fn (mixed $id): int => (int) $id)
            ->unique()
            ->values()
            ->all();

        $commentIds = $rows
            ->filter(fn (UserActivity $activity): bool => $activity->entity_type === UserActivity::ENTITY_ARTWORK_COMMENT)
            ->pluck('entity_id')
            ->map(fn (mixed $id): int => (int) $id)
            ->unique()
            ->values()
            ->all();

        $userIds = $rows
            ->filter(fn (UserActivity $activity): bool => $activity->entity_type === UserActivity::ENTITY_USER)
            ->pluck('entity_id')
            ->map(fn (mixed $id): int => (int) $id)
            ->unique()
            ->values()
            ->all();

        $achievementIds = $rows
            ->filter(fn (UserActivity $activity): bool => $activity->entity_type === UserActivity::ENTITY_ACHIEVEMENT)
            ->pluck('entity_id')
            ->map(fn (mixed $id): int => (int) $id)
            ->unique()
            ->values()
            ->all();

        $forumThreadIds = $rows
            ->filter(fn (UserActivity $activity): bool => $activity->entity_type === UserActivity::ENTITY_FORUM_THREAD)
            ->pluck('entity_id')
            ->map(fn (mixed $id): int => (int) $id)
            ->unique()
            ->values()
            ->all();

        $forumPostIds = $rows
            ->filter(fn (UserActivity $activity): bool => $activity->entity_type === UserActivity::ENTITY_FORUM_POST)
            ->pluck('entity_id')
            ->map(fn (mixed $id): int => (int) $id)
            ->unique()
            ->values()
            ->all();

        return [
            'artworks' => empty($artworkIds)
                ? collect()
                : Artwork::query()
                    ->with(['stats'])
                    ->whereIn('id', $artworkIds)
                    ->public()
                    ->published()
                    ->whereNull('deleted_at')
                    ->get()
                    ->keyBy('id'),
            'comments' => empty($commentIds)
                ? collect()
                : ArtworkComment::query()
                    ->with(['artwork.stats'])
                    ->whereIn('id', $commentIds)
                    ->where('is_approved', true)
                    ->whereNull('deleted_at')
                    ->whereHas('artwork', fn ($query) => $query->public()->published()->whereNull('deleted_at'))
                    ->get()
                    ->keyBy('id'),
            'users' => empty($userIds)
                ? collect()
                : User::query()
                    ->with('profile:user_id,avatar_hash')
                    ->withCount('artworks')
                    ->whereIn('id', $userIds)
                    ->where('is_active', true)
                    ->whereNull('deleted_at')
                    ->get()
                    ->keyBy('id'),
            'achievements' => empty($achievementIds)
                ? collect()
                : Achievement::query()
                    ->whereIn('id', $achievementIds)
                    ->get()
                    ->keyBy('id'),
            'forum_threads' => empty($forumThreadIds)
                ? collect()
                : ForumThread::query()
                    ->with('category:id,name,slug')
                    ->whereIn('id', $forumThreadIds)
                    ->where('visibility', 'public')
                    ->whereNull('deleted_at')
                    ->get()
                    ->keyBy('id'),
            'forum_posts' => empty($forumPostIds)
                ? collect()
                : ForumPost::query()
                    ->with(['thread.category:id,name,slug'])
                    ->whereIn('id', $forumPostIds)
                    ->whereNull('deleted_at')
                    ->where('flagged', false)
                    ->whereHas('thread', fn ($query) => $query->where('visibility', 'public')->whereNull('deleted_at'))
                    ->get()
                    ->keyBy('id'),
        ];
    }

    private function formatActivity(UserActivity $activity, User $actor, array $related): ?array
    {
        $base = [
            'id' => (int) $activity->id,
            'type' => (string) $activity->type,
            'entity_type' => (string) $activity->entity_type,
            'created_at' => $activity->created_at?->toIso8601String(),
            'time_ago' => $activity->created_at?->diffForHumans(),
            'actor' => $this->buildUserPayload($actor),
            'meta' => is_array($activity->meta) ? $activity->meta : [],
        ];

        return match ($activity->type) {
            UserActivity::TYPE_UPLOAD,
            UserActivity::TYPE_LIKE,
            UserActivity::TYPE_FAVOURITE => $this->formatArtworkActivity($base, $activity, $related),
            UserActivity::TYPE_COMMENT,
            UserActivity::TYPE_REPLY => $this->formatCommentActivity($base, $activity, $related),
            UserActivity::TYPE_FOLLOW => $this->formatFollowActivity($base, $activity, $related),
            UserActivity::TYPE_ACHIEVEMENT => $this->formatAchievementActivity($base, $activity, $related),
            UserActivity::TYPE_FORUM_POST,
            UserActivity::TYPE_FORUM_REPLY => $this->formatForumActivity($base, $activity, $related),
            default => null,
        };
    }

    private function formatArtworkActivity(array $base, UserActivity $activity, array $related): ?array
    {
        /** @var Artwork|null $artwork */
        $artwork = $related['artworks']->get((int) $activity->entity_id);
        if (! $artwork) {
            return null;
        }

        return [
            ...$base,
            'artwork' => $this->buildArtworkPayload($artwork),
        ];
    }

    private function formatCommentActivity(array $base, UserActivity $activity, array $related): ?array
    {
        /** @var ArtworkComment|null $comment */
        $comment = $related['comments']->get((int) $activity->entity_id);
        if (! $comment || ! $comment->artwork) {
            return null;
        }

        return [
            ...$base,
            'artwork' => $this->buildArtworkPayload($comment->artwork),
            'comment' => [
                'id' => (int) $comment->id,
                'parent_id' => $comment->parent_id ? (int) $comment->parent_id : null,
                'body' => $this->plainTextExcerpt((string) ($comment->raw_content ?? $comment->content ?? '')),
                'url' => route('art.show', ['id' => (int) $comment->artwork_id, 'slug' => Str::slug((string) $comment->artwork->slug ?: (string) $comment->artwork->title)]) . '#comment-' . $comment->id,
            ],
        ];
    }

    private function formatFollowActivity(array $base, UserActivity $activity, array $related): ?array
    {
        /** @var User|null $target */
        $target = $related['users']->get((int) $activity->entity_id);
        if (! $target) {
            return null;
        }

        return [
            ...$base,
            'target_user' => $this->buildUserPayload($target),
        ];
    }

    private function formatAchievementActivity(array $base, UserActivity $activity, array $related): ?array
    {
        /** @var Achievement|null $achievement */
        $achievement = $related['achievements']->get((int) $activity->entity_id);
        if (! $achievement) {
            return null;
        }

        return [
            ...$base,
            'achievement' => [
                'id' => (int) $achievement->id,
                'name' => (string) $achievement->name,
                'slug' => (string) $achievement->slug,
                'description' => (string) ($achievement->description ?? ''),
                'icon' => (string) ($achievement->icon ?? 'fa-solid fa-trophy'),
                'xp_reward' => (int) ($achievement->xp_reward ?? 0),
            ],
        ];
    }

    private function formatForumActivity(array $base, UserActivity $activity, array $related): ?array
    {
        if ($activity->type === UserActivity::TYPE_FORUM_POST) {
            /** @var ForumThread|null $thread */
            $thread = $related['forum_threads']->get((int) $activity->entity_id);
            if (! $thread) {
                return null;
            }

            return [
                ...$base,
                'forum' => [
                    'thread' => $this->buildForumThreadPayload($thread),
                    'post' => null,
                ],
            ];
        }

        /** @var ForumPost|null $post */
        $post = $related['forum_posts']->get((int) $activity->entity_id);
        if (! $post || ! $post->thread) {
            return null;
        }

        return [
            ...$base,
            'forum' => [
                'thread' => $this->buildForumThreadPayload($post->thread),
                'post' => [
                    'id' => (int) $post->id,
                    'excerpt' => $this->plainTextExcerpt((string) $post->content),
                    'url' => $this->forumThreadUrl($post->thread) . '#post-' . $post->id,
                ],
            ],
        ];
    }

    private function buildArtworkPayload(Artwork $artwork): array
    {
        $slug = Str::slug((string) ($artwork->slug ?: $artwork->title));
        if ($slug === '') {
            $slug = (string) $artwork->id;
        }

        return [
            'id' => (int) $artwork->id,
            'title' => html_entity_decode((string) ($artwork->title ?? 'Artwork'), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
            'url' => route('art.show', ['id' => (int) $artwork->id, 'slug' => $slug]),
            'thumb' => $artwork->thumbUrl('md') ?? $artwork->thumbnail_url,
            'stats' => [
                'views' => (int) ($artwork->stats?->views ?? 0),
                'likes' => (int) ($artwork->stats?->favorites ?? 0),
                'comments' => (int) ($artwork->stats?->comments_count ?? 0),
            ],
        ];
    }

    private function buildUserPayload(User $user): array
    {
        $username = (string) ($user->username ?? '');

        return [
            'id' => (int) $user->id,
            'name' => html_entity_decode((string) ($user->name ?? $username ?: 'User'), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
            'username' => $username,
            'profile_url' => $username !== '' ? route('profile.show', ['username' => strtolower($username)]) : null,
            'avatar_url' => AvatarUrl::forUser((int) $user->id, $user->profile?->avatar_hash, 64),
            'badge' => $this->resolveBadge($user),
        ];
    }

    private function buildForumThreadPayload(ForumThread $thread): array
    {
        return [
            'id' => (int) $thread->id,
            'title' => $this->plainText((string) $thread->title),
            'url' => $this->forumThreadUrl($thread),
            'category_name' => $this->plainText((string) ($thread->category?->name ?? 'Forum')),
            'category_slug' => (string) ($thread->category?->slug ?? ''),
        ];
    }

    private function plainTextExcerpt(string $content, int $limit = 220): string
    {
        $text = $this->plainText($content);

        return Str::limit($text, $limit, '...');
    }

    private function plainText(string $value): string
    {
        return trim((string) (preg_replace('/\s+/', ' ', strip_tags($this->decodeHtml($value))) ?? ''));
    }

    private function decodeHtml(string $value): string
    {
        $decoded = $value;

        for ($pass = 0; $pass < 5; $pass++) {
            $next = html_entity_decode($decoded, ENT_QUOTES | ENT_HTML5, 'UTF-8');

            if ($next === $decoded) {
                break;
            }

            $decoded = $next;
        }

        return str_replace(['´', '&acute;'], ["'", "'"], $decoded);
    }

    private function forumThreadUrl(ForumThread $thread): string
    {
        $topic = (string) ($thread->slug ?: $thread->id);

        if (Route::has('forum.topic.show')) {
            return (string) route('forum.topic.show', ['topic' => $topic]);
        }

        if (Route::has('forum.thread.show')) {
            return (string) route('forum.thread.show', ['thread' => $thread->id, 'slug' => $thread->slug ?: $thread->id]);
        }

        return '/forum/topic/' . $topic;
    }

    private function resolveBadge(User $user): ?array
    {
        if ($user->hasRole('admin')) {
            return ['label' => 'Admin', 'tone' => 'rose'];
        }

        if ($user->hasRole('moderator')) {
            return ['label' => 'Moderator', 'tone' => 'amber'];
        }

        if ((int) ($user->artworks_count ?? 0) > 0) {
            return ['label' => 'Creator', 'tone' => 'sky'];
        }

        return null;
    }

    private function typesForFilter(string $filter): array
    {
        return match ($filter) {
            self::FILTER_UPLOADS => [UserActivity::TYPE_UPLOAD],
            self::FILTER_COMMENTS => [UserActivity::TYPE_COMMENT, UserActivity::TYPE_REPLY],
            self::FILTER_LIKES => [UserActivity::TYPE_LIKE, UserActivity::TYPE_FAVOURITE],
            self::FILTER_FORUM => [UserActivity::TYPE_FORUM_POST, UserActivity::TYPE_FORUM_REPLY],
            self::FILTER_FOLLOWING => [UserActivity::TYPE_FOLLOW],
            default => [
                UserActivity::TYPE_UPLOAD,
                UserActivity::TYPE_COMMENT,
                UserActivity::TYPE_REPLY,
                UserActivity::TYPE_LIKE,
                UserActivity::TYPE_FAVOURITE,
                UserActivity::TYPE_FOLLOW,
                UserActivity::TYPE_ACHIEVEMENT,
                UserActivity::TYPE_FORUM_POST,
                UserActivity::TYPE_FORUM_REPLY,
            ],
        };
    }

    private function cacheVersion(int $userId): int
    {
        return (int) Cache::get($this->versionKey($userId), self::FEED_SCHEMA_VERSION);
    }

    private function bumpCacheVersion(int $userId): void
    {
        $key = $this->versionKey($userId);
        Cache::add($key, self::FEED_SCHEMA_VERSION, now()->addDays(7));
        Cache::increment($key);
    }

    private function versionKey(int $userId): string
    {
        return 'user_activity_feed_version:v' . self::FEED_SCHEMA_VERSION . ':' . $userId;
    }
}
