<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Artwork;
use App\Models\Group;
use App\Models\GroupActivityItem;
use App\Models\GroupAsset;
use App\Models\GroupChallenge;
use App\Models\GroupEvent;
use App\Models\GroupPost;
use App\Models\GroupProject;
use App\Models\GroupRelease;
use App\Models\User;
use Illuminate\Support\Collection;

class GroupActivityService
{
    public function record(
        Group $group,
        ?User $actor,
        string $type,
        string $subjectType,
        ?int $subjectId,
        string $headline,
        ?string $summary = null,
        string $visibility = GroupActivityItem::VISIBILITY_PUBLIC,
    ): GroupActivityItem {
        return GroupActivityItem::query()->create([
            'group_id' => (int) $group->id,
            'type' => $type,
            'visibility' => $visibility,
            'actor_user_id' => $actor?->id,
            'subject_type' => $subjectType,
            'subject_id' => $subjectId,
            'headline' => $headline,
            'summary' => $summary,
            'is_pinned' => false,
            'occurred_at' => now(),
        ]);
    }

    public function pin(GroupActivityItem $item, User $actor, bool $isPinned = true): GroupActivityItem
    {
        $item->forceFill([
            'is_pinned' => $isPinned,
        ])->save();

        app(GroupHistoryService::class)->record(
            $item->group,
            $actor,
            $isPinned ? 'activity_pinned' : 'activity_unpinned',
            sprintf('%s group activity item.', $isPinned ? 'Pinned' : 'Unpinned'),
            'group_activity_item',
            (int) $item->id,
            ['is_pinned' => ! $isPinned],
            ['is_pinned' => $isPinned],
        );

        return $item->fresh(['actor']);
    }

    public function publicFeed(Group $group, int $limit = 8): array
    {
        return $this->mapItems(
            GroupActivityItem::query()
                ->with('actor:id,name,username')
                ->where('group_id', $group->id)
                ->where('visibility', GroupActivityItem::VISIBILITY_PUBLIC)
                ->orderByDesc('is_pinned')
                ->orderByDesc('occurred_at')
                ->limit(max(1, min(24, $limit)))
                ->get(),
            $group
        );
    }

    public function studioFeed(Group $group, User $viewer, int $limit = 20): array
    {
        if (! $group->canViewStudio($viewer)) {
            return [];
        }

        return $this->mapItems(
            GroupActivityItem::query()
                ->with('actor:id,name,username')
                ->where('group_id', $group->id)
                ->orderByDesc('is_pinned')
                ->orderByDesc('occurred_at')
                ->limit(max(1, min(50, $limit)))
                ->get(),
            $group
        );
    }

    private function mapItems(Collection $items, Group $group): array
    {
        $subjects = $this->loadSubjects($items);

        return $items->map(function (GroupActivityItem $item) use ($group, $subjects): array {
            $subject = $subjects[$item->subject_type][$item->subject_id] ?? null;

            return [
                'id' => (int) $item->id,
                'type' => (string) $item->type,
                'visibility' => (string) $item->visibility,
                'headline' => (string) $item->headline,
                'summary' => $item->summary,
                'is_pinned' => (bool) $item->is_pinned,
                'occurred_at' => $item->occurred_at?->toISOString(),
                'actor' => $item->actor ? [
                    'id' => (int) $item->actor->id,
                    'name' => $item->actor->name,
                    'username' => $item->actor->username,
                ] : null,
                'subject' => $subject ? [
                    'type' => (string) $item->subject_type,
                    'id' => (int) $item->subject_id,
                    'title' => $subject->title ?? null,
                    'url' => $this->subjectUrl($group, (string) $item->subject_type, $subject),
                ] : null,
            ];
        })->values()->all();
    }

    private function loadSubjects(Collection $items): array
    {
        $grouped = $items
            ->filter(fn (GroupActivityItem $item): bool => $item->subject_id !== null)
            ->groupBy('subject_type')
            ->map(fn (Collection $chunk): array => $chunk->pluck('subject_id')->map(fn ($id): int => (int) $id)->unique()->values()->all());

        return [
            'artwork' => Artwork::query()->whereIn('id', $grouped->get('artwork', []))->get()->keyBy('id')->all(),
            'group_post' => GroupPost::query()->whereIn('id', $grouped->get('group_post', []))->get()->keyBy('id')->all(),
            'group_project' => GroupProject::query()->whereIn('id', $grouped->get('group_project', []))->get()->keyBy('id')->all(),
            'group_release' => GroupRelease::query()->whereIn('id', $grouped->get('group_release', []))->get()->keyBy('id')->all(),
            'group_challenge' => GroupChallenge::query()->whereIn('id', $grouped->get('group_challenge', []))->get()->keyBy('id')->all(),
            'group_event' => GroupEvent::query()->whereIn('id', $grouped->get('group_event', []))->get()->keyBy('id')->all(),
            'group_asset' => GroupAsset::query()->whereIn('id', $grouped->get('group_asset', []))->get()->keyBy('id')->all(),
        ];
    }

    private function subjectUrl(Group $group, string $subjectType, object $subject): ?string
    {
        return match ($subjectType) {
            'artwork' => route('art.show', ['id' => $subject->id, 'slug' => $subject->slug ?: $subject->id]),
            'group_post' => route('groups.posts.show', ['group' => $group, 'post' => $subject]),
            'group_project' => route('groups.projects.show', ['group' => $group, 'project' => $subject]),
            'group_release' => route('groups.releases.show', ['group' => $group, 'release' => $subject]),
            'group_challenge' => route('groups.challenges.show', ['group' => $group, 'challenge' => $subject]),
            'group_event' => route('groups.events.show', ['group' => $group, 'event' => $subject]),
            'group_asset' => route('groups.assets.download', ['group' => $group, 'asset' => $subject]),
            default => null,
        };
    }
}