<?php

declare(strict_types=1);

namespace App\Http\Controllers\Web;

use App\Enums\ReactionType;
use App\Http\Controllers\Controller;
use App\Http\Resources\ArtworkResource;
use App\Models\Artwork;
use App\Models\ArtworkComment;
use Illuminate\Support\Facades\DB;
use App\Services\ContentSanitizer;
use App\Services\ThumbnailPresenter;
use App\Services\ErrorSuggestionService;
use App\Services\GroupService;
use App\Services\Maturity\ArtworkMaturityService;
use App\Support\Seo\SeoFactory;
use App\Support\AvatarUrl;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Inertia\Inertia;
use Inertia\Response as InertiaResponse;

final class ArtworkPageController extends Controller
{
    public function __construct(
        private readonly GroupService $groups,
        private readonly ArtworkMaturityService $maturity,
    ) {}

    public function show(Request $request, int $id, ?string $slug = null): View|RedirectResponse|Response|InertiaResponse
    {
        // ── Step 1: check existence including soft-deleted ─────────────────
        $raw = Artwork::withTrashed()->where('id', $id)->first();

        if (! $raw) {
            // Artwork never existed → contextual 404
            $suggestions = app(ErrorSuggestionService::class);
            return response(view('errors.contextual.artwork-not-found', [
                'trendingArtworks' => $this->safeSuggestions(fn () => $suggestions->trendingArtworks()),
            ]), 404);
        }

        if ($raw->trashed()) {
            // Artwork permanently deleted → 410 Gone
            return response(view('errors.410'), 410);
        }

        if (! $raw->is_public
            || ! $raw->is_approved
            || (string) ($raw->visibility ?? '') === Artwork::VISIBILITY_PRIVATE
            || $raw->published_at === null
            || $raw->published_at->isFuture()) {
            // Artwork exists but is private/unapproved → 403 Forbidden.
            // Show other public artworks by the same creator as recovery suggestions.
            $suggestions = app(ErrorSuggestionService::class);
            $creatorArtworks = collect();
            $creatorUsername = null;

            if ($raw->user_id) {
                $raw->loadMissing('user');
                $creatorUsername = $raw->user?->username;

                $creatorArtworks = $this->safeSuggestions(function () use ($raw) {
                    return Artwork::query()
                        ->with('user')
                        ->where('user_id', $raw->user_id)
                        ->where('id', '!=', $raw->id)
                        ->catalogVisible()
                        ->limit(6)
                        ->get()
                        ->map(function (Artwork $a) {
                            $slug = \Illuminate\Support\Str::slug((string) ($a->slug ?: $a->title)) ?: (string) $a->id;
                            $md   = \App\Services\ThumbnailPresenter::present($a, 'md');
                            return [
                                'id'     => $a->id,
                                'title'  => html_entity_decode((string) $a->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
                                'author' => html_entity_decode((string) ($a->user?->name ?: $a->user?->username ?: 'Artist'), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
                                'url'    => route('art.show', ['id' => $a->id, 'slug' => $slug]),
                                'thumb'  => $md['url'] ?? null,
                            ];
                        });
                });
            }

            return response(view('errors.contextual.artwork-not-found', [
                'message'          => 'This artwork is not publicly available.',
                'isForbidden'      => true,
                'creatorArtworks'  => $creatorArtworks,
                'creatorUsername'  => $creatorUsername,
                'trendingArtworks' => $this->safeSuggestions(fn () => $suggestions->trendingArtworks()),
            ]), 403);
        }

        // ── Step 2: full load with all relations ───────────────────────────
        $artwork = Artwork::with(['user.profile', 'group.owner.profile', 'uploadedBy.profile', 'primaryAuthor.profile', 'contributors.user.profile', 'categories.contentType', 'categories.parent.contentType', 'tags', 'stats', 'awardStat'])
            ->where('id', $id)
            ->public()
            ->published()
            ->firstOrFail();

        $this->loadCategoryAncestors($artwork->categories);

        $canonicalSlug = Str::slug((string) ($artwork->slug ?: $artwork->title));
        if ($canonicalSlug === '') {
            $canonicalSlug = (string) $artwork->id;
        }

        if ((string) $slug !== $canonicalSlug) {
            return redirect()->route('art.show', [
                'id' => $artwork->id,
                'slug' => $canonicalSlug,
            ], 301);
        }

        $thumbMd = ThumbnailPresenter::present($artwork, 'md');
        $thumbLg = ThumbnailPresenter::present($artwork, 'lg');
        $thumbXl = ThumbnailPresenter::present($artwork, 'xl');
        $thumbSq = ThumbnailPresenter::present($artwork, 'sq');

        $artworkData = (new ArtworkResource($artwork))->toArray($request);
        $groupSummary = null;

        if ($artwork->group) {
            $artwork->group->loadMissing(['owner.profile', 'recruitmentProfile', 'discoveryMetric', 'members', 'badges']);
            $groupSummary = $this->groups->mapGroupCard($artwork->group, $request->user());
        }

        $canonical = route('art.show', ['id' => $artwork->id, 'slug' => $canonicalSlug]);
        $authorName = $artwork->group?->name ?: $artwork->primaryAuthor?->name ?: $artwork->primaryAuthor?->username ?: $artwork->user?->name ?: $artwork->user?->username ?: 'Artist';
        $description = Str::limit(trim(strip_tags(html_entity_decode((string) ($artwork->description ?? ''), ENT_QUOTES | ENT_HTML5, 'UTF-8'))), 160, '…');

        $meta = [
            'title' => sprintf('%s by %s — Skinbase', html_entity_decode((string) $artwork->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'), html_entity_decode((string) $authorName, ENT_QUOTES | ENT_HTML5, 'UTF-8')),
            'description' => $description !== '' ? $description : html_entity_decode((string) $artwork->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
            'canonical' => $canonical,
            'og_image' => $thumbXl['url'] ?? $thumbLg['url'] ?? null,
            'og_width' => $thumbXl['width'] ?? $thumbLg['width'] ?? null,
            'og_height' => $thumbXl['height'] ?? $thumbLg['height'] ?? null,
        ];

        $seo = app(SeoFactory::class)->artwork($artwork, [
            'md' => $thumbMd,
            'lg' => $thumbLg,
            'xl' => $thumbXl,
        ], $canonical, $this->artworkBreadcrumbs($artwork, $canonical))->toArray();

        $categoryIds = $artwork->categories->pluck('id')->filter()->values();
        $tagIds = $artwork->tags->pluck('id')->filter()->values();

        $related = Artwork::query()
            ->with(['user', 'group', 'categories.contentType'])
            ->whereKeyNot($artwork->id)
            ->public()
            ->published()
            ->tap(fn ($builder) => $this->maturity->applyViewerFilter($builder, $request->user()))
            ->where(function ($query) use ($artwork, $categoryIds, $tagIds): void {
                $query->where('user_id', $artwork->user_id);

                if ($artwork->group_id) {
                    $query->orWhere('group_id', $artwork->group_id);
                }

                if ($categoryIds->isNotEmpty()) {
                    $query->orWhereHas('categories', function ($categoryQuery) use ($categoryIds): void {
                        $categoryQuery->whereIn('categories.id', $categoryIds->all());
                    });
                }

                if ($tagIds->isNotEmpty()) {
                    $query->orWhereHas('tags', function ($tagQuery) use ($tagIds): void {
                        $tagQuery->whereIn('tags.id', $tagIds->all());
                    });
                }
            })
            ->latest('published_at')
            ->limit(12)
            ->get()
            ->map(function (Artwork $item): array {
                $itemSlug = Str::slug((string) ($item->slug ?: $item->title));
                if ($itemSlug === '') {
                    $itemSlug = (string) $item->id;
                }

                $sm = ThumbnailPresenter::present($item, 'sm');
                $md = ThumbnailPresenter::present($item, 'md');

                return $this->maturity->decoratePayload([
                    'id' => (int) $item->id,
                    'title' => html_entity_decode((string) $item->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
                    'author' => html_entity_decode((string) ($item->group?->name ?: $item->user?->name ?: $item->user?->username ?: 'Artist'), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
                    'author_id' => (int) ($item->user?->id ?? 0),
                    'publisher_type' => $item->group ? 'group' : 'user',
                    'publisher_id' => $item->group ? (int) $item->group->id : (int) ($item->user?->id ?? 0),
                    'url' => route('art.show', ['id' => $item->id, 'slug' => $itemSlug]),
                    'thumb' => $sm['url'] ?? null,
                    'thumb_srcset' => ($sm['url'] ?? '') . ' 320w, ' . ($md['url'] ?? '') . ' 640w',
                ], $item, request()->user());
            })
            ->values()
            ->all();

        $approvedComments = ArtworkComment::query()
            ->with('user.profile')
            ->where('artwork_id', $artwork->id)
            ->where('is_approved', true)
            ->orderBy('created_at')
            ->limit(500)
            ->get();

        $commentsByParent = $approvedComments->groupBy(
            static fn (ArtworkComment $comment): string => $comment->parent_id === null
                ? 'root'
                : (string) $comment->parent_id
        );

        // Recursive helper to format a comment and its nested replies.
        $formatComment = null;
        $formatComment = function (ArtworkComment $c) use (&$formatComment, $commentsByParent): array {
            /** @var Collection<int, ArtworkComment> $replies */
            $replies = $commentsByParent->get((string) $c->id, collect());
            $user = $c->user;
            $userId = (int) ($c->user_id ?? 0);
            $avatarHash = $user?->profile?->avatar_hash ?? null;
            $canPublishLinks = (int) ($user?->level ?? 1) > 1 && strtolower((string) ($user?->rank ?? 'Newbie')) !== 'newbie';
            $rawContent = (string) ($c->raw_content ?? $c->content ?? '');
            $renderedContent = $c->rendered_content;

            if (! is_string($renderedContent) || trim($renderedContent) === '') {
                $renderedContent = $rawContent !== ''
                    ? ContentSanitizer::render($rawContent)
                    : nl2br(e(strip_tags((string) ($c->content ?? ''))));
            }

            return [
                'id'               => $c->id,
                'parent_id'        => $c->parent_id,
                'content'          => html_entity_decode((string) $c->content, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
                'raw_content'      => $c->raw_content ?? $c->content,
                'rendered_content' => ContentSanitizer::sanitizeRenderedHtml($renderedContent, $canPublishLinks),
                'created_at'       => $c->created_at?->toIso8601String(),
                'time_ago'         => $c->created_at ? Carbon::parse($c->created_at)->diffForHumans() : null,
                'user'             => [
                    'id'          => $userId,
                    'name'        => $user?->name,
                    'username'    => $user?->username,
                    'display'     => $user?->username ?? $user?->name ?? 'User',
                    'profile_url' => $user?->username ? '/@' . $user->username : ($userId > 0 ? '/profile/' . $userId : null),
                    'avatar_url'  => $avatarHash !== null
                        ? AvatarUrl::forUser($userId, $avatarHash, 64)
                        : AvatarUrl::default(),
                    'level'       => (int) ($user?->level ?? 1),
                    'rank'        => (string) ($user?->rank ?? 'Newbie'),
                ],
                'replies' => $replies->map($formatComment)->values()->all(),
            ];
        };

        $comments = $commentsByParent
            ->get('root', collect())
            ->map($formatComment)
            ->values()
            ->all();

        $canReadSession = $request->hasSession() && ! $request->attributes->get('skinbase.session_skipped');

        $userId = ($canReadSession && $request->user() !== null) ? (int) $request->user()->id : null;

        return Inertia::render('ArtworkPage', [
            'artwork'         => $artworkData,
            'presentMd'       => $thumbMd,
            'presentLg'       => $thumbLg,
            'presentXl'       => $thumbXl,
            'presentSq'       => $thumbSq,
            'related'         => $related,
            'canonicalUrl'    => $canonical,
            'comments'        => $comments,
            'groupSummary'    => $groupSummary,
            'isAuthenticated' => $userId !== null,
            'reactionTotals'  => $this->artworkReactionTotals((int) $artwork->id, $userId),
            'seo'             => $seo,
        ])->rootView('artworks.show');
    }

    /**
     * Build per-slug reaction totals for the given artwork, including
     * whether the given user has each reaction (mine=true).
     *
     * Mirrors ReactionController::getTotals() so the page can render
     * the correct state without a separate client-side fetch on first load.
     */
    private function artworkReactionTotals(int $artworkId, ?int $userId): array
    {
        $rows = DB::table('artwork_reactions')
            ->where('artwork_id', $artworkId)
            ->selectRaw('reaction, COUNT(*) as total')
            ->groupBy('reaction')
            ->get()
            ->keyBy('reaction');

        $totals = [];
        foreach (ReactionType::cases() as $type) {
            $slug  = $type->value;
            $count = (int) ($rows[$slug]->total ?? 0);

            $mine = false;
            if ($userId !== null && $count > 0) {
                $mine = DB::table('artwork_reactions')
                    ->where('artwork_id', $artworkId)
                    ->where('reaction', $slug)
                    ->where('user_id', $userId)
                    ->exists();
            }

            $totals[$slug] = [
                'emoji' => $type->emoji(),
                'label' => $type->label(),
                'count' => $count,
                'mine'  => $mine,
            ];
        }

        return $totals;
    }

    private function loadCategoryAncestors(Collection $categories): void
    {
        $currentLevel = $categories->filter();

        while ($currentLevel->isNotEmpty()) {
            $fetchedParents = collect();
            $missingParentIds = $currentLevel
                ->filter(static fn ($category) => $category->parent_id !== null && ! $category->relationLoaded('parent'))
                ->pluck('parent_id')
                ->filter()
                ->unique()
                ->values();

            if ($missingParentIds->isNotEmpty()) {
                $fetchedParents = \App\Models\Category::query()
                    ->with('contentType')
                    ->whereIn('id', $missingParentIds->all())
                    ->get()
                    ->keyBy('id');

                $currentLevel->each(function ($category) use ($fetchedParents): void {
                    if ($category->parent_id !== null && ! $category->relationLoaded('parent')) {
                        $category->setRelation('parent', $fetchedParents->get($category->parent_id));
                    }
                });
            }

            $currentLevel = $currentLevel
                ->map(static fn ($category) => $category->relationLoaded('parent') ? $category->getRelation('parent') : null)
                ->filter()
                ->unique('id')
                ->values();
        }
    }

    /**
     * @return array<int, array{name: string, url: string}>
     */
    private function artworkBreadcrumbs(Artwork $artwork, string $canonical): array
    {
        $primaryCategory = $artwork->categories
            ->sortBy(fn ($category) => [
                (int) ($category->sort_order ?? 0),
                (string) ($category->name ?? ''),
            ])
            ->first();

        if ($primaryCategory === null) {
            return [
                ['name' => 'Explore', 'url' => url('/explore')],
                ['name' => html_entity_decode((string) $artwork->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'url' => $canonical],
            ];
        }

        $contentType = $primaryCategory->contentType;
        $chain = collect();
        $current = $primaryCategory;

        while ($current !== null) {
            $chain->prepend($current);
            $current = $current->relationLoaded('parent') ? $current->parent : null;
        }

        $breadcrumbs = [];
        $contentTypeSlug = trim((string) ($contentType?->slug ?? ''));
        $contentTypeName = trim((string) ($contentType?->name ?? ''));

        if ($contentTypeSlug !== '' && $contentTypeName !== '') {
            $breadcrumbs[] = [
                'name' => $contentTypeName,
                'url' => url('/' . $contentTypeSlug),
            ];
        }

        $pathSegments = [];

        foreach ($chain as $category) {
            $slug = trim((string) ($category->slug ?? ''));
            $name = trim((string) ($category->name ?? ''));

            if ($slug === '' || $name === '' || $contentTypeSlug === '') {
                continue;
            }

            $pathSegments[] = $slug;
            $breadcrumbs[] = [
                'name' => $name,
                'url' => url('/' . $contentTypeSlug . '/' . implode('/', $pathSegments)),
            ];
        }

        $breadcrumbs[] = [
            'name' => html_entity_decode((string) $artwork->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
            'url' => $canonical,
        ];

        return $breadcrumbs;
    }

    /** Silently catch suggestion query failures so error page never crashes. */
    private function safeSuggestions(callable $fn): mixed
    {
        try {
            return $fn();
        } catch (\Throwable) {
            return collect();
        }
    }
}
