<?php

declare(strict_types=1);

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Models\Artwork;
use App\Services\Maturity\ArtworkMaturityService;
use App\Services\Recommendations\HybridSimilarArtworksService;
use App\Services\ThumbnailPresenter;
use App\Services\Vision\VectorService;
use App\Support\AvatarUrl;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use RuntimeException;

/**
 * GET /art/{id}/similar
 *
 * Renders a full gallery page of artworks similar to the given source artwork.
 *
 * Priority:
 *   1. Qdrant visual similarity  (VectorService / vision gateway)
 *   2. HybridSimilarArtworksService (precomputed tag, behavior, hybrid)
 *   3. Meilisearch tag-overlap + category fallback
 */
final class SimilarArtworksPageController extends Controller
{
    private const PER_PAGE = 24;

    /** How many candidates to request from Qdrant (> PER_PAGE to allow pagination) */
    private const QDRANT_LIMIT = 120;

    public function __construct(
        private readonly VectorService              $vectors,
        private readonly ArtworkMaturityService     $maturity,
        private readonly HybridSimilarArtworksService $hybridService,
    ) {}

    public function __invoke(Request $request, int $id)
    {
        // ── Load source artwork ────────────────────────────────────────────────
        $source = Artwork::public()
            ->published()
            ->with([
                'tags:id,slug',
                'categories:id,slug,name',
                'categories.contentType:id,name,slug',
                'user:id,name,username',
                'user.profile:user_id,avatar_hash',
            ])
            ->findOrFail($id);

        $baseUrl = url("/art/{$id}/similar");

        // ── Normalise source artwork for the view ──────────────────────────────
        $primaryCat  = $source->categories->sortBy('sort_order')->first();
        $sourceMd    = ThumbnailPresenter::present($source, 'md');
        $sourceLg    = ThumbnailPresenter::present($source, 'lg');
        $sourceTitle = html_entity_decode((string) ($source->title ?? 'Artwork'), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $sourceUrl   = route('art.show', ['id' => $source->id, 'slug' => $source->slug]);

        $sourceCard = (object) [
            'id'                => $source->id,
            'title'             => $sourceTitle,
            'url'               => $sourceUrl,
            'thumb_md'          => $sourceMd['url'] ?? null,
            'thumb_lg'          => $sourceLg['url'] ?? null,
            'thumb_srcset'      => $sourceMd['srcset'] ?? $sourceMd['url'] ?? null,
            'author_name'       => $source->user?->name ?? 'Artist',
            'author_username'   => $source->user?->username ?? '',
            'author_profile_url'=> $source->user?->username ? '/@' . $source->user->username : null,
            'author_avatar'     => AvatarUrl::forUser(
                                       (int) ($source->user_id ?? 0),
                                       $source->user?->profile?->avatar_hash ?? null,
                                       80
                                   ),
            'category_name'     => $primaryCat?->name ?? '',
            'category_slug'     => $primaryCat?->slug ?? '',
            'content_type_name' => $primaryCat?->contentType?->name ?? '',
            'content_type_slug' => $primaryCat?->contentType?->slug ?? '',
            'browse_url'        => $primaryCat?->contentType?->slug ? url('/' . $primaryCat->contentType->slug) : url('/explore'),
            'tag_slugs'         => $source->tags->pluck('slug')->take(5)->all(),
            'width'             => $source->width ?? null,
            'height'            => $source->height ?? null,
        ];

        return view('gallery.similar', [
            'sourceArtwork'         => $sourceCard,
            'gallery_type'          => 'similar',
            'gallery_nav_section'   => 'artworks',
            'mainCategories'        => collect(),
            'subcategories'         => collect(),
            'contentType'           => null,
            'category'              => null,
            'spotlight'             => collect(),
            'current_sort'          => 'trending',
            'sort_options'          => [],
            'page_title'            => 'Similar to "' . $sourceTitle . '" — Skinbase',
            'page_meta_description' => 'Discover artworks similar to "' . $sourceTitle . '" on Skinbase.',
            'page_canonical'        => $baseUrl,
            'page_robots'           => 'index,follow',
            'breadcrumbs'           => collect([
                (object) ['name' => 'Explore',           'url' => '/explore'],
                (object) ['name' => $sourceTitle,        'url' => $sourceUrl],
                (object) ['name' => 'Similar Artworks',  'url' => $baseUrl],
            ]),
        ]);
    }

    /**
     * GET /art/{id}/similar-results (JSON)
     *
     * Returns paginated similar artworks asynchronously so the page shell
     * can render instantly while this slower query runs in the background.
     */
    public function results(Request $request, int $id): JsonResponse
    {
        $source = Artwork::public()
            ->published()
            ->with([
                'tags:id,slug',
                'categories:id,slug,name',
                'categories.contentType:id,name,slug',
                'user:id,name,username',
                'user.profile:user_id,avatar_hash',
            ])
            ->findOrFail($id);

        $page    = max(1, (int) $request->query('page', 1));
        $baseUrl = url("/art/{$id}/similar");

        [$artworks, $similaritySource] = $this->resolveSimilarArtworks($source, $page, $baseUrl);

        $galleryItems = $artworks->getCollection()->map(fn ($art) => [
            'id'            => $art->id            ?? null,
            'name'          => $art->name          ?? null,
            'thumb'         => $art->thumb_url     ?? $art->thumb ?? null,
            'thumb_srcset'  => $art->thumb_srcset  ?? null,
            'uname'         => $art->uname         ?? '',
            'username'      => $art->username      ?? $art->uname ?? '',
            'avatar_url'    => $art->avatar_url    ?? null,
            'category_name' => $art->category_name ?? '',
            'category_slug' => $art->category_slug ?? '',
            'slug'          => $art->slug          ?? '',
            'width'         => $art->width         ?? null,
            'height'        => $art->height        ?? null,
            'maturity'      => $art->maturity      ?? null,
        ])->values();

        $galleryItems = collect($this->maturity->filterPayloadItems($galleryItems->all(), $request->user()))->values();

        return response()->json([
            'data'              => $galleryItems,
            'similarity_source' => $similaritySource,
            'total'             => $artworks->total(),
            'current_page'      => $artworks->currentPage(),
            'last_page'         => $artworks->lastPage(),
            'next_page_url'     => $artworks->nextPageUrl(),
            'prev_page_url'     => $artworks->previousPageUrl(),
        ]);
    }

    // ── Similarity resolution ──────────────────────────────────────────────────

    /**
     * @return array{0: LengthAwarePaginator, 1: string}
     */
    private function resolveSimilarArtworks(Artwork $source, int $page, string $baseUrl): array
    {
        // Priority 1 — Qdrant visual (vision) similarity
        if ($this->vectors->isConfigured()) {
            $qdrantItems = $this->resolveViaQdrant($source);
            if ($qdrantItems !== null && $qdrantItems->isNotEmpty()) {
                $paginator = $this->paginateCollection(
                    $qdrantItems->map(fn ($a) => $this->presentArtwork($a)),
                    $page,
                    $baseUrl,
                );
                return [$paginator, 'visual'];
            }
        }

        // Priority 2 — precomputed hybrid list (tag / behavior / AI)
        $hybridItems = $this->hybridService->forArtwork($source->id, self::QDRANT_LIMIT);
        if ($hybridItems->isNotEmpty()) {
            $paginator = $this->paginateCollection(
                $hybridItems->map(fn ($a) => $this->presentArtwork($a)),
                $page,
                $baseUrl,
            );
            return [$paginator, 'hybrid'];
        }

        // Priority 3 — Meilisearch tag/category overlap
        $paginator = $this->meilisearchFallback($source, $page);
        return [$paginator, 'tags'];
    }

    /**
     * Query Qdrant via VectorGateway, then re-hydrate full Artwork models
     * (so we have category/dimension data for the masonry grid).
     *
     * Returns null when the gateway call fails, so the caller can fall through.
     */
    private function resolveViaQdrant(Artwork $source): ?Collection
    {
        try {
            $raw = $this->vectors->similarToArtwork($source, self::QDRANT_LIMIT);
        } catch (RuntimeException) {
            return null;
        }

        if (empty($raw)) {
            return null;
        }

        // Preserve Qdrant relevance order; IDs are already filtered to public+published
        $orderedIds = array_column($raw, 'id');

        $artworks = Artwork::query()
            ->whereIn('id', $orderedIds)
            ->where('id', '!=', $source->id)   // belt-and-braces exclusion
            ->public()
            ->published()
            ->with([
                'categories:id,slug,name',
                'categories.contentType:id,name,slug',
                'user:id,name,username',
                'user.profile:user_id,avatar_hash',
            ])
            ->get()
            ->keyBy('id');

        return collect($orderedIds)
            ->map(fn (int $id) => $artworks->get($id))
            ->filter()
            ->values();
    }

    /**
     * Meilisearch tag-overlap query with category fallback.
     */
    private function meilisearchFallback(Artwork $source, int $page): LengthAwarePaginator
    {
        $tagSlugs      = $source->tags->pluck('slug')->values()->all();
        $categorySlugs = $source->categories->pluck('slug')->values()->all();

        $filterParts = [
            'is_public = true',
            'is_approved = true',
            'id != ' . $source->id,
        ];

        if ($tagSlugs !== []) {
            $quoted        = array_map(fn (string $t): string => 'tags = "' . addslashes($t) . '"', $tagSlugs);
            $filterParts[] = '(' . implode(' OR ', $quoted) . ')';
        } elseif ($categorySlugs !== []) {
            $quoted        = array_map(fn (string $c): string => 'category = "' . addslashes($c) . '"', $categorySlugs);
            $filterParts[] = '(' . implode(' OR ', $quoted) . ')';
        }

        $results = Artwork::search('')->options([
            'filter' => implode(' AND ', $filterParts),
            'sort'   => ['trending_score_7d:desc', 'created_at:desc'],
        ])->paginate(self::PER_PAGE, 'page', $page);

        $results->getCollection()->transform(fn ($a) => $this->presentArtwork($a));

        return $results;
    }

    /**
     * Wrap a Collection into a LengthAwarePaginator for the view.
     */
    private function paginateCollection(
        Collection $items,
        int        $page,
        string     $path,
    ): LengthAwarePaginator {
        $perPage = self::PER_PAGE;
        $total   = $items->count();
        $slice   = $items->forPage($page, $perPage)->values();

        return new LengthAwarePaginator($slice, $total, $perPage, $page, [
            'path'  => $path,
            'query' => [],
        ]);
    }

    // ── Presenter ─────────────────────────────────────────────────────────────

    private function presentArtwork(Artwork $artwork): object
    {
        $primary   = $artwork->categories->sortBy('sort_order')->first();
        $present   = ThumbnailPresenter::present($artwork, 'md');
        $group     = $artwork->group;
        $isGroupPublisher = $group !== null;
        $avatarUrl = $isGroupPublisher
            ? $group->avatarUrl()
            : AvatarUrl::forUser(
                (int) ($artwork->user_id ?? 0),
                $artwork->user?->profile?->avatar_hash ?? null,
                64
            );
        $displayName = $isGroupPublisher ? ($group->name ?? 'Skinbase') : ($artwork->user?->name ?? 'Skinbase');
        $username = $isGroupPublisher ? '' : ($artwork->user?->username ?? '');
        $profileUrl = $isGroupPublisher ? $group->publicUrl() : ($username !== '' ? '/@' . $username : null);

        return (object) $this->maturity->decoratePayload([
            'id'                => $artwork->id,
            'name'              => $artwork->title,
            'content_type_name' => $primary?->contentType?->name ?? '',
            'content_type_slug' => $primary?->contentType?->slug ?? '',
            'category_name'     => $primary?->name ?? '',
            'category_slug'     => $primary?->slug ?? '',
            'thumb_url'         => $present['url'],
            'thumb_srcset'      => $present['srcset'] ?? $present['url'],
            'uname'             => $displayName,
            'username'          => $username,
            'avatar_url'        => $avatarUrl,
            'profile_url'       => $profileUrl,
            'published_as_type' => $isGroupPublisher ? 'group' : 'user',
            'publisher'         => [
                'type' => $isGroupPublisher ? 'group' : 'user',
                'name' => $displayName,
                'username' => $username,
                'avatar_url' => $avatarUrl,
                'profile_url' => $profileUrl,
            ],
            'published_at'      => $artwork->published_at,
            'slug'              => $artwork->slug ?? '',
            'width'             => $artwork->width  ?? null,
            'height'            => $artwork->height ?? null,
        ], $artwork, request()->user());
    }
}
