<?php

declare(strict_types=1);

namespace App\Support\Moderation;

use App\Models\Artwork;
use App\Models\ArtworkComment;
use App\Models\Collection;
use App\Models\CollectionComment;
use App\Models\CollectionSubmission;
use App\Models\ConversationParticipant;
use App\Models\Group;
use App\Models\GroupPost;
use App\Models\Message;
use App\Models\NovaCard;
use App\Models\NovaCardChallenge;
use App\Models\NovaCardChallengeEntry;
use App\Models\NovaCardComment;
use App\Models\Report;
use App\Models\Story;
use App\Models\StoryComment;
use App\Models\User;
use App\Services\NovaCards\NovaCardPublishModerationService;
use Illuminate\Database\Eloquent\ModelNotFoundException;

class ReportTargetResolver
{
    public function __construct(
        private readonly NovaCardPublishModerationService $moderation,
    ) {
    }

    /**
     * @return array<int, string>
     */
    public function supportedTargetTypes(): array
    {
        return [
            'message',
            'conversation',
            'user',
            'group',
            'group_post',
            'story',
            'story_comment',
            'artwork_comment',
            'collection',
            'collection_comment',
            'collection_submission',
            ...$this->novaCardTargetTypes(),
        ];
    }

    /**
     * @return array<int, string>
     */
    public function novaCardTargetTypes(): array
    {
        return [
            'nova_card',
            'nova_card_comment',
            'nova_card_challenge',
            'nova_card_challenge_entry',
        ];
    }

    public function validateForReporter(User $user, string $targetType, int $targetId): void
    {
        switch ($targetType) {
            case 'message':
                $message = Message::query()->findOrFail($targetId);
                $allowed = ConversationParticipant::query()
                    ->where('conversation_id', $message->conversation_id)
                    ->where('user_id', $user->id)
                    ->whereNull('left_at')
                    ->exists();
                abort_unless($allowed, 403, 'You are not allowed to report this message.');
                return;

            case 'conversation':
                $allowed = ConversationParticipant::query()
                    ->where('conversation_id', $targetId)
                    ->where('user_id', $user->id)
                    ->whereNull('left_at')
                    ->exists();
                abort_unless($allowed, 403, 'You are not allowed to report this conversation.');
                return;

            case 'user':
                User::query()->findOrFail($targetId);
                return;

            case 'group':
                $group = Group::query()->findOrFail($targetId);
                abort_unless($group->canBeViewedBy($user), 403, 'You are not allowed to report this group.');
                return;

            case 'group_post':
                $post = GroupPost::query()->with('group')->findOrFail($targetId);
                abort_unless(
                    $post->group !== null
                    && $post->status === GroupPost::STATUS_PUBLISHED
                    && $post->group->canBeViewedBy($user),
                    403,
                    'You are not allowed to report this group post.'
                );
                return;

            case 'story':
                Story::query()->findOrFail($targetId);
                return;

            case 'story_comment':
                $storyComment = StoryComment::query()->with('story')->findOrFail($targetId);
                abort_unless(
                    $storyComment->story !== null
                    && Story::query()->published()->whereKey($storyComment->story_id)->exists(),
                    403,
                    'You are not allowed to report this comment.'
                );
                return;

            case 'artwork_comment':
                $artworkComment = ArtworkComment::query()->with('artwork')->findOrFail($targetId);
                $artwork = $artworkComment->artwork;
                abort_unless($artwork instanceof Artwork && (bool) $artwork->is_public && $artwork->published_at !== null, 403, 'You are not allowed to report this comment.');
                return;

            case 'collection':
                $collection = Collection::query()->findOrFail($targetId);
                abort_unless($collection->canBeViewedBy($user), 403, 'You are not allowed to report this collection.');
                return;

            case 'collection_comment':
                $comment = CollectionComment::query()->with('collection')->findOrFail($targetId);
                abort_unless($comment->collection && $comment->collection->canBeViewedBy($user), 403, 'You are not allowed to report this comment.');
                return;

            case 'collection_submission':
                $submission = CollectionSubmission::query()->with('collection')->findOrFail($targetId);
                abort_unless($submission->collection && $submission->collection->canBeViewedBy($user), 403, 'You are not allowed to report this submission.');
                return;

            case 'nova_card':
                $card = NovaCard::query()->with('user')->findOrFail($targetId);
                abort_unless($card->canBeViewedBy($user), 403, 'You are not allowed to report this card.');
                return;

            case 'nova_card_comment':
                $comment = NovaCardComment::query()->with('card.user')->findOrFail($targetId);
                abort_unless($comment->card !== null && $comment->card->canBeViewedBy($user), 403, 'You are not allowed to report this card comment.');
                return;

            case 'nova_card_challenge':
                $challenge = NovaCardChallenge::query()->findOrFail($targetId);
                abort_unless(in_array($challenge->status, [NovaCardChallenge::STATUS_ACTIVE, NovaCardChallenge::STATUS_COMPLETED, NovaCardChallenge::STATUS_ARCHIVED], true), 404);
                return;

            case 'nova_card_challenge_entry':
                $entry = NovaCardChallengeEntry::query()->with(['challenge', 'card.user'])->findOrFail($targetId);
                abort_unless($entry->challenge !== null && $entry->card !== null, 404);
                abort_unless(in_array($entry->challenge->status, [NovaCardChallenge::STATUS_ACTIVE, NovaCardChallenge::STATUS_COMPLETED, NovaCardChallenge::STATUS_ARCHIVED], true), 404);
                abort_unless($entry->card->canBeViewedBy($user), 403, 'You are not allowed to report this challenge entry.');
                return;

            default:
                throw (new ModelNotFoundException())->setModel(Report::class, [$targetId]);
        }
    }

    public function summarize(Report $report): array
    {
        $summary = [
            'exists' => false,
            'type' => (string) $report->target_type,
            'id' => (int) $report->target_id,
            'label' => 'Unavailable target',
            'subtitle' => 'The original target could not be loaded.',
            'public_url' => null,
            'moderation_url' => null,
            'moderation_target' => null,
        ];

        try {
            return match ($report->target_type) {
                'group' => $this->summarizeGroup($report->target_id),
                'group_post' => $this->summarizeGroupPost($report->target_id),
                'nova_card' => $this->summarizeNovaCard($report->target_id),
                'nova_card_comment' => $this->summarizeNovaCardComment($report->target_id),
                'nova_card_challenge' => $this->summarizeNovaCardChallenge($report->target_id),
                'nova_card_challenge_entry' => $this->summarizeNovaCardChallengeEntry($report->target_id),
                default => $summary,
            };
        } catch (ModelNotFoundException) {
            return $summary;
        }
    }

    public function resolveModerationCard(Report $report): ?NovaCard
    {
        return match ($report->target_type) {
            'nova_card' => NovaCard::query()->find($report->target_id),
            'nova_card_comment' => NovaCardComment::query()->with('card')->find($report->target_id)?->card,
            'nova_card_challenge_entry' => NovaCardChallengeEntry::query()->with('card')->find($report->target_id)?->card,
            default => null,
        };
    }

    private function summarizeGroup(int $targetId): array
    {
        $group = Group::query()->with('owner')->findOrFail($targetId);

        return [
            'exists' => true,
            'type' => 'group',
            'id' => (int) $group->id,
            'label' => (string) $group->name,
            'subtitle' => trim(sprintf('@%s%s', (string) $group->owner?->username, $group->headline ? ' • '.$group->headline : '')),
            'public_url' => $group->publicUrl(),
            'moderation_url' => null,
            'moderation_target' => null,
        ];
    }

    private function summarizeGroupPost(int $targetId): array
    {
        $post = GroupPost::query()->with(['group.owner'])->findOrFail($targetId);

        return [
            'exists' => $post->group !== null,
            'type' => 'group_post',
            'id' => (int) $post->id,
            'label' => (string) $post->title,
            'subtitle' => trim(sprintf('%s • %s', (string) $post->group?->name, ucfirst((string) $post->type))),
            'public_url' => $post->group ? route('groups.posts.show', ['group' => $post->group, 'post' => $post]) : null,
            'moderation_url' => null,
            'moderation_target' => null,
        ];
    }

    private function summarizeNovaCard(int $targetId): array
    {
        $card = NovaCard::query()
            ->with(['user', 'category'])
            ->findOrFail($targetId);

        return [
            'exists' => true,
            'type' => 'nova_card',
            'id' => (int) $card->id,
            'label' => (string) $card->title,
            'subtitle' => trim(sprintf('@%s%s', (string) $card->user?->username, $card->category ? ' • '.$card->category->name : '')),
            'public_url' => $card->publicUrl(),
            'moderation_url' => route('cp.cards.index'),
            'moderation_target' => $this->summarizeModerationCard($card),
        ];
    }

    private function summarizeNovaCardChallenge(int $targetId): array
    {
        $challenge = NovaCardChallenge::query()->findOrFail($targetId);

        return [
            'exists' => true,
            'type' => 'nova_card_challenge',
            'id' => (int) $challenge->id,
            'label' => (string) $challenge->title,
            'subtitle' => sprintf('%s challenge • %d entries', ucfirst((string) $challenge->status), (int) $challenge->entries_count),
            'public_url' => route('cards.challenges.show', ['slug' => $challenge->slug]),
            'moderation_url' => route('cp.cards.challenges.index'),
            'moderation_target' => null,
        ];
    }

    private function summarizeNovaCardComment(int $targetId): array
    {
        $comment = NovaCardComment::query()
            ->with(['card.user'])
            ->findOrFail($targetId);

        return [
            'exists' => $comment->card !== null,
            'type' => 'nova_card_comment',
            'id' => (int) $comment->id,
            'label' => 'Comment on ' . ($comment->card?->title ?? 'Nova Card'),
            'subtitle' => trim(sprintf('@%s • %s', (string) $comment->user?->username, str($comment->body)->limit(80))),
            'public_url' => $comment->card ? $comment->card->publicUrl() . '#comment-' . $comment->id : null,
            'moderation_url' => route('cp.cards.index'),
            'moderation_target' => $comment->card ? $this->summarizeModerationCard($comment->card, 'Card attached to this comment') : null,
        ];
    }

    private function summarizeNovaCardChallengeEntry(int $targetId): array
    {
        $entry = NovaCardChallengeEntry::query()
            ->with(['challenge', 'card.user'])
            ->findOrFail($targetId);

        return [
            'exists' => $entry->challenge !== null && $entry->card !== null,
            'type' => 'nova_card_challenge_entry',
            'id' => (int) $entry->id,
            'label' => (string) ($entry->card?->title ?? 'Challenge entry'),
            'subtitle' => trim(sprintf('Entry for %s%s', $entry->challenge?->title ? '"'.$entry->challenge->title.'"' : 'challenge', $entry->card?->user?->username ? ' • @'.$entry->card->user->username : '')),
            'public_url' => $entry->card?->publicUrl(),
            'moderation_url' => $entry->challenge ? route('cp.cards.challenges.index') : route('cp.cards.index'),
            'moderation_target' => $entry->card ? $this->summarizeModerationCard($entry->card, 'Card attached to this challenge entry') : null,
        ];
    }

    private function summarizeModerationCard(NovaCard $card, ?string $context = null): array
    {
        $moderationReasons = $this->moderation->storedReasons($card);
        $moderationOverride = $this->moderation->latestOverride($card);

        return [
            'kind' => 'nova_card',
            'card_id' => (int) $card->id,
            'title' => (string) $card->title,
            'context' => $context,
            'status' => (string) $card->status,
            'moderation_status' => (string) $card->moderation_status,
            'moderation_reasons' => $moderationReasons,
            'moderation_reason_labels' => $this->moderation->labelsFor($moderationReasons),
            'moderation_source' => $this->moderation->storedSource($card),
            'moderation_override' => $moderationOverride,
            'moderation_override_history' => $this->moderation->overrideHistory($card),
            'available_actions' => [
                ['action' => 'approve_card', 'label' => 'Approve card'],
                ['action' => 'flag_card', 'label' => 'Flag card'],
                ['action' => 'reject_card', 'label' => 'Reject card'],
            ],
        ];
    }
}