<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Artwork;
use App\Models\Category;
use App\Models\Group;
use App\Jobs\IndexArtworkJob;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class GroupArtworkReviewService
{
    public function __construct(
        private readonly ArtworkAttributionService $attribution,
        private readonly GroupHistoryService $history,
        private readonly NotificationService $notifications,
        private readonly GroupMembershipService $memberships,
    ) {
    }

    public function submit(Group $group, Artwork $artwork, User $actor, array $attributes): Artwork
    {
        if (! $group->canSubmitArtworkForReview($actor)) {
            throw ValidationException::withMessages([
                'group' => 'You are not allowed to submit artwork for this group.',
            ]);
        }

        if ($group->status !== Group::LIFECYCLE_ACTIVE) {
            throw ValidationException::withMessages([
                'group' => 'Archived or suspended groups cannot accept new submissions.',
            ]);
        }

        if ((int) $artwork->user_id !== (int) $actor->id && (int) ($artwork->uploaded_by_user_id ?? 0) !== (int) $actor->id) {
            throw ValidationException::withMessages([
                'artwork' => 'You can only submit your own group draft for review.',
            ]);
        }

        $before = [
            'group_review_status' => $artwork->group_review_status,
            'artwork_status' => $artwork->artwork_status,
        ];

        $this->applyDraftMetadata($artwork, $actor, $attributes);
        $artwork->save();
        $artwork = $this->attribution->apply($artwork->fresh(['group.members']), $actor, $attributes, false);

        $artwork->forceFill([
            'visibility' => (string) ($attributes['visibility'] ?? $artwork->visibility ?? Artwork::VISIBILITY_PUBLIC),
            'is_public' => false,
            'is_approved' => false,
            'published_at' => null,
            'publish_at' => null,
            'artwork_status' => 'draft',
            'group_review_status' => 'submitted',
            'group_review_submitted_at' => now(),
            'group_reviewed_by_user_id' => null,
            'group_reviewed_at' => null,
            'group_review_notes' => null,
        ])->save();

        $this->syncSearchIndex($artwork);

        $this->history->record(
            $group,
            $actor,
            'artwork_submitted_for_review',
            sprintf('Submitted "%s" for group review.', $artwork->title),
            'artwork',
            (int) $artwork->id,
            $before,
            [
                'group_review_status' => 'submitted',
                'visibility' => $artwork->visibility,
            ],
        );

        foreach ($this->reviewRecipients($group, $actor->id) as $recipient) {
            $this->notifications->notifyGroupArtworkSubmittedForReview($recipient, $actor, $group, $artwork);
        }

        return $artwork->fresh(['group', 'uploadedBy.profile', 'primaryAuthor.profile', 'contributors.user.profile']);
    }

    public function approve(Group $group, Artwork $artwork, User $actor, ?string $notes = null): Artwork
    {
        $this->guardReviewAbility($group, $artwork, $actor);

        $before = [
            'group_review_status' => $artwork->group_review_status,
            'artwork_status' => $artwork->artwork_status,
            'published_at' => optional($artwork->published_at)->toISOString(),
        ];

        $artwork->forceFill([
            'group_review_status' => 'approved',
            'group_reviewed_by_user_id' => $actor->id,
            'group_reviewed_at' => now(),
            'group_review_notes' => $notes,
            'is_approved' => true,
            'artwork_status' => 'published',
            'published_at' => now(),
            'publish_at' => null,
            'is_public' => ($artwork->visibility ?: Artwork::VISIBILITY_PUBLIC) !== Artwork::VISIBILITY_PRIVATE,
        ])->save();

        $this->syncSearchIndex($artwork);

        $this->history->record(
            $group,
            $actor,
            'artwork_submission_approved',
            sprintf('Approved group submission "%s".', $artwork->title),
            'artwork',
            (int) $artwork->id,
            $before,
            [
                'group_review_status' => 'approved',
                'artwork_status' => 'published',
                'published_at' => optional($artwork->published_at)->toISOString(),
            ],
        );

        app(GroupActivityService::class)->record(
            $group,
            $actor,
            'artwork_published',
            'artwork',
            (int) $artwork->id,
            sprintf('%s published new artwork: %s', $group->name, $artwork->title),
            $notes,
            'public',
        );

        if ($artwork->uploadedBy) {
            $this->notifications->notifyGroupArtworkApproved($artwork->uploadedBy, $actor, $group, $artwork);
        }

        return $artwork->fresh(['group', 'uploadedBy.profile', 'primaryAuthor.profile', 'contributors.user.profile']);
    }

    public function requestChanges(Group $group, Artwork $artwork, User $actor, ?string $notes = null): Artwork
    {
        $this->guardReviewAbility($group, $artwork, $actor);

        $before = [
            'group_review_status' => $artwork->group_review_status,
        ];

        $artwork->forceFill([
            'group_review_status' => 'needs_changes',
            'group_reviewed_by_user_id' => $actor->id,
            'group_reviewed_at' => now(),
            'group_review_notes' => $notes,
            'is_public' => false,
            'published_at' => null,
            'artwork_status' => 'draft',
        ])->save();

        $this->syncSearchIndex($artwork);

        $this->history->record(
            $group,
            $actor,
            'artwork_submission_changes_requested',
            sprintf('Requested changes for "%s".', $artwork->title),
            'artwork',
            (int) $artwork->id,
            $before,
            ['group_review_status' => 'needs_changes'],
        );

        if ($artwork->uploadedBy) {
            $this->notifications->notifyGroupArtworkNeedsChanges($artwork->uploadedBy, $actor, $group, $artwork);
        }

        return $artwork->fresh(['group', 'uploadedBy.profile', 'primaryAuthor.profile', 'contributors.user.profile']);
    }

    public function reject(Group $group, Artwork $artwork, User $actor, ?string $notes = null): Artwork
    {
        $this->guardReviewAbility($group, $artwork, $actor);

        $before = [
            'group_review_status' => $artwork->group_review_status,
        ];

        $artwork->forceFill([
            'group_review_status' => 'rejected',
            'group_reviewed_by_user_id' => $actor->id,
            'group_reviewed_at' => now(),
            'group_review_notes' => $notes,
            'is_public' => false,
            'published_at' => null,
            'artwork_status' => 'draft',
        ])->save();

        $this->syncSearchIndex($artwork);

        $this->history->record(
            $group,
            $actor,
            'artwork_submission_rejected',
            sprintf('Rejected group submission "%s".', $artwork->title),
            'artwork',
            (int) $artwork->id,
            $before,
            ['group_review_status' => 'rejected'],
        );

        if ($artwork->uploadedBy) {
            $this->notifications->notifyGroupArtworkRejected($artwork->uploadedBy, $actor, $group, $artwork);
        }

        return $artwork->fresh(['group', 'uploadedBy.profile', 'primaryAuthor.profile', 'contributors.user.profile']);
    }

    public function pendingCount(Group $group): int
    {
        return (int) Artwork::query()
            ->where('group_id', $group->id)
            ->where('group_review_status', 'submitted')
            ->whereNull('deleted_at')
            ->count();
    }

    public function listing(Group $group, User $viewer, array $filters = []): array
    {
        $bucket = (string) ($filters['bucket'] ?? 'submitted');
        $page = max(1, (int) ($filters['page'] ?? 1));
        $perPage = min(max((int) ($filters['per_page'] ?? 20), 10), 50);
        $canReviewAll = $group->canReviewSubmissions($viewer);

        $query = Artwork::query()
            ->with(['uploadedBy.profile', 'primaryAuthor.profile'])
            ->where('group_id', $group->id)
            ->whereNull('deleted_at')
            ->whereIn('group_review_status', ['submitted', 'needs_changes', 'approved', 'rejected']);

        if (! $canReviewAll) {
            $query->where(function ($builder) use ($viewer): void {
                $builder->where('uploaded_by_user_id', $viewer->id)
                    ->orWhere('user_id', $viewer->id);
            });
        }

        if ($bucket !== 'all') {
            $query->where('group_review_status', $bucket);
        }

        $paginator = $query->orderByRaw("CASE group_review_status WHEN 'submitted' THEN 0 WHEN 'needs_changes' THEN 1 ELSE 2 END")
            ->orderByDesc('group_review_submitted_at')
            ->paginate($perPage, ['*'], 'page', $page);

        return [
            'items' => collect($paginator->items())->map(fn (Artwork $artwork): array => $this->mapReviewItem($group, $artwork, $viewer))->values()->all(),
            'meta' => [
                'current_page' => $paginator->currentPage(),
                'last_page' => $paginator->lastPage(),
                'per_page' => $paginator->perPage(),
                'total' => $paginator->total(),
            ],
            'filters' => [
                'bucket' => $bucket,
            ],
            'bucket_options' => [
                ['value' => 'submitted', 'label' => 'Submitted'],
                ['value' => 'needs_changes', 'label' => 'Needs changes'],
                ['value' => 'approved', 'label' => 'Approved'],
                ['value' => 'rejected', 'label' => 'Rejected'],
                ['value' => 'all', 'label' => 'All'],
            ],
            'can_review_all' => $canReviewAll,
        ];
    }

    public function mapReviewItem(Group $group, Artwork $artwork, User $viewer): array
    {
        return [
            'id' => (int) $artwork->id,
            'title' => (string) $artwork->title,
            'thumb' => $artwork->thumbUrl('sm'),
            'group_review_status' => (string) ($artwork->group_review_status ?: 'none'),
            'group_review_notes' => $artwork->group_review_notes,
            'submitted_at' => $artwork->group_review_submitted_at?->toISOString(),
            'reviewed_at' => $artwork->group_reviewed_at?->toISOString(),
            'visibility' => (string) ($artwork->visibility ?: Artwork::VISIBILITY_PUBLIC),
            'uploader' => $artwork->uploadedBy ? [
                'id' => (int) $artwork->uploadedBy->id,
                'name' => $artwork->uploadedBy->name,
                'username' => $artwork->uploadedBy->username,
            ] : null,
            'primary_author' => $artwork->primaryAuthor ? [
                'id' => (int) $artwork->primaryAuthor->id,
                'name' => $artwork->primaryAuthor->name,
                'username' => $artwork->primaryAuthor->username,
            ] : null,
            'urls' => [
                'edit' => route('studio.artworks.edit', ['id' => $artwork->id]),
                'approve' => route('studio.groups.artworks.approve', ['group' => $group, 'artwork' => $artwork]),
                'reject' => route('studio.groups.artworks.reject', ['group' => $group, 'artwork' => $artwork]),
                'needs_changes' => route('studio.groups.artworks.needs-changes', ['group' => $group, 'artwork' => $artwork]),
            ],
            'can_review' => $group->canReviewSubmissions($viewer),
        ];
    }

    private function guardReviewAbility(Group $group, Artwork $artwork, User $actor): void
    {
        if ((int) ($artwork->group_id ?? 0) !== (int) $group->id) {
            throw ValidationException::withMessages([
                'artwork' => 'This artwork does not belong to the selected group.',
            ]);
        }

        if (! $group->canReviewSubmissions($actor)) {
            throw ValidationException::withMessages([
                'group' => 'You are not allowed to review submissions for this group.',
            ]);
        }

        if (! in_array((string) $artwork->group_review_status, ['submitted', 'needs_changes'], true)) {
            throw ValidationException::withMessages([
                'artwork' => 'This artwork is not currently awaiting review.',
            ]);
        }
    }

    private function applyDraftMetadata(Artwork $artwork, User $actor, array $validated): void
    {
        $title = trim((string) ($validated['title'] ?? $artwork->title ?? ''));
        if ($title === '') {
            $title = 'Untitled artwork';
        }

        $slugBase = Str::slug($title);
        if ($slugBase === '') {
            $slugBase = 'artwork';
        }

        $artwork->title = $title;

        if (array_key_exists('description', $validated)) {
            $artwork->description = $validated['description'];
        }

        if (array_key_exists('is_mature', $validated)) {
            $artwork->is_mature = (bool) $validated['is_mature'];
        }

        $artwork->slug = Str::limit($slugBase, 160, '');
        $artwork->artwork_timezone = $validated['timezone'] ?? $artwork->artwork_timezone;
        $artwork->uploaded_by_user_id = $artwork->uploaded_by_user_id ?: (int) $actor->id;
        $artwork->primary_author_user_id = $artwork->primary_author_user_id ?: (int) $actor->id;

        $categoryId = isset($validated['category']) ? (int) $validated['category'] : null;
        if ($categoryId > 0 && Category::query()->where('id', $categoryId)->exists()) {
            $artwork->categories()->sync([$categoryId]);
        }

        if (array_key_exists('tags', $validated) && is_array($validated['tags'])) {
            $tagIds = [];
            foreach ($validated['tags'] as $tagSlug) {
                $tag = Tag::firstOrCreate(
                    ['slug' => Str::slug((string) $tagSlug)],
                    ['name' => (string) $tagSlug, 'is_active' => true, 'usage_count' => 0]
                );
                $tagIds[$tag->id] = ['source' => 'user', 'confidence' => 1.0];
            }
            $artwork->tags()->sync($tagIds);
        }
    }

    private function reviewRecipients(Group $group, int $excludeUserId): array
    {
        return User::query()
            ->whereIn('id', $this->memberships->activeContributorIds($group))
            ->get()
            ->filter(fn (User $member): bool => (int) $member->id !== $excludeUserId && $group->canReviewSubmissions($member))
            ->values()
            ->all();
    }

    private function syncSearchIndex(Artwork $artwork): void
    {
        IndexArtworkJob::dispatch((int) $artwork->id);
    }
}