<?php

declare(strict_types=1);

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Models\Artwork;
use App\Models\Group;
use App\Models\User;
use App\Services\ArtworkSearchService;
use App\Services\ContentTypes\ContentTypeSlugResolver;
use App\Services\EarlyGrowth\EarlyGrowth;
use App\Services\EarlyGrowth\GridFiller;
use App\Services\EarlyGrowth\SpotlightEngineInterface;
use App\Services\Maturity\ArtworkMaturityService;
use App\Services\ThumbnailPresenter;
use Illuminate\Http\Request;
use Illuminate\Pagination\AbstractCursorPaginator;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;

/**
 * ExploreController
 *
 * Powers the /explore/* structured catalog pages (§3.2 of routing spec).
 * Delegates to the same Meilisearch pipeline as BrowseGalleryController but
 * uses canonical /explore/* URLs with the ExploreLayout blade template.
 */
final class ExploreController extends Controller
{
    /** Meilisearch sort-field arrays per sort alias. */
    private const SORT_MAP = [
        'trending'   => ['trending_score_24h:desc', 'trending_score_7d:desc', 'favorites_count:desc', 'created_at:desc'],
        'fresh'      => ['trending_score_7d:desc', 'favorites_count:desc', 'created_at:desc'],
        'top-rated'  => ['awards_received_count:desc', 'favorites_count:desc'],
        'latest'     => ['created_at:desc'],
        // Legacy aliases kept for backward compatibility.
        'new-hot'    => ['trending_score_7d:desc', 'favorites_count:desc', 'created_at:desc'],
        'best'       => ['awards_received_count:desc', 'favorites_count:desc'],
    ];

    private const SORT_TTL = [
        'trending' => 300,
        'fresh'    => 120,
        'top-rated'=> 600,
        'latest'   => 120,
        'new-hot'  => 120,
        'best'     => 600,
    ];

    private const SORT_OPTIONS = [
        ['value' => 'trending',  'label' => '🔥 Trending'],
        ['value' => 'fresh',     'label' => '🚀 New & Hot'],
        ['value' => 'top-rated', 'label' => '⭐ Best'],
        ['value' => 'latest',    'label' => '🕐 Latest'],
    ];

    private const SORT_ALIASES = [
        'new-hot' => 'fresh',
        'best' => 'top-rated',
    ];

    public function __construct(
        private readonly ArtworkSearchService  $search,
        private readonly GridFiller            $gridFiller,
        private readonly SpotlightEngineInterface $spotlight,
        private readonly ContentTypeSlugResolver $contentTypeResolver,
        private readonly ArtworkMaturityService $maturity,
    ) {}

    // ── /explore (hub) ──────────────────────────────────────────────────

    public function index(Request $request)
    {
        $sort    = $this->resolveSort($request);
        $perPage = $this->resolvePerPage($request);
        $page    = max(1, (int) $request->query('page', 1));
        $ttl     = self::SORT_TTL[$sort] ?? 300;
        $cacheVersion = $this->cacheVersion();
        $filter = $this->buildExploreFilterExpression($request);
        $cacheSuffix = $this->requestCacheSuffix($request);

        $artworks = Cache::remember("explore.all.v{$cacheVersion}.{$cacheSuffix}.{$page}", $ttl, fn () =>
            $this->search->searchWithThumbnailPreference([
                'filter' => $filter,
                'sort'   => self::SORT_MAP[$sort] ?? ['created_at:desc'],
            ], $perPage, false, $page)
        );
        $artworks = $this->filterBrowsableArtworks($artworks);
        // EGS: fill grid to minimum when uploads are sparse
        $artworks = $this->gridFiller->fill($artworks, 0, $page);
        $this->loadPresentationRelations($artworks->getCollection());
        $artworks->getCollection()->transform(fn ($a) => $this->presentArtwork($a));

        // EGS §11: featured spotlight row on page 1 only
        $spotlightItems = collect();

        if ($page === 1 && EarlyGrowth::spotlightEnabled()) {
            $spotlightItems = $this->spotlight->getSpotlight(6);
            $this->loadPresentationRelations($spotlightItems);
            $spotlightItems = $spotlightItems->map(fn ($a) => $this->presentArtwork($a));
        }

        $mainCategories = $this->mainCategories();
        $seo = $this->paginationSeo($request, url('/explore'), $artworks);

        return view('gallery.index', [
            'gallery_type'          => 'browse',
            'mainCategories'        => $mainCategories,
            'subcategories'         => $mainCategories,
            'contentType'           => null,
            'category'              => null,
            'artworks'              => $artworks,
            'spotlight'             => $spotlightItems,
            'current_sort'          => $sort,
            'sort_options'          => self::SORT_OPTIONS,
            'hero_title'            => 'Explore',
            'hero_description'      => 'Browse the full Skinbase catalog — wallpapers, skins, photography and more.',
            'breadcrumbs'           => collect([(object) ['name' => 'Explore', 'url' => '/explore']]),
            'page_title'            => 'Explore Artworks - Skinbase',
            'page_meta_description' => 'Explore the full catalog of wallpapers, skins, photography and other artworks on Skinbase.',
            'page_meta_keywords'    => 'explore, wallpapers, skins, photography, artworks, skinbase',
            'page_canonical'        => $seo['canonical'],
            'page_rel_prev'         => $seo['prev'],
            'page_rel_next'         => $seo['next'],
            'page_robots'           => 'index,follow',
        ]);
    }

    // ── /explore/:type ──────────────────────────────────────────────────

    public function byType(Request $request, string $type)
    {
        $resolution = $this->contentTypeResolver->resolve($type, allowVirtual: true);

        if (! $resolution->found()) {
            abort(404);
        }

        $isAll = $resolution->isVirtual && $resolution->virtualType === 'artworks';

        if (! $isAll && $resolution->contentType === null) {
            abort(404);
        }

        $resolvedTypeSlug = $isAll ? 'artworks' : strtolower((string) $resolution->contentType->slug);

        // Canonical URLs for content types are /skins, /wallpapers, /photography, /other.
        if (! $isAll) {
            return redirect()->to($this->canonicalTypeUrl($request, $resolvedTypeSlug), 301);
        }

        $sort    = $this->resolveSort($request);
        $perPage = $this->resolvePerPage($request);
        $page    = max(1, (int) $request->query('page', 1));
        $ttl     = self::SORT_TTL[$sort] ?? 300;
        $cacheVersion = $this->cacheVersion();
        $filter = $this->buildExploreFilterExpression($request, $isAll ? null : $resolvedTypeSlug);
        $cacheSuffix = $this->requestCacheSuffix($request);

        $artworks = Cache::remember("explore.{$resolvedTypeSlug}.v{$cacheVersion}.{$cacheSuffix}.{$page}", $ttl, fn () =>
            $this->search->searchWithThumbnailPreference([
                'filter' => $filter,
                'sort'   => self::SORT_MAP[$sort] ?? ['created_at:desc'],
            ], $perPage, false, $page)
        );
        $artworks = $this->filterBrowsableArtworks($artworks);
        // EGS: fill grid to minimum when uploads are sparse
        $artworks = $this->gridFiller->fill($artworks, 0, $page);
        $this->loadPresentationRelations($artworks->getCollection());
        $artworks->getCollection()->transform(fn ($a) => $this->presentArtwork($a));

        // EGS §11: featured spotlight row on page 1 only
        $spotlightItems = collect();

        if ($page === 1 && EarlyGrowth::spotlightEnabled()) {
            $spotlightItems = $this->spotlight->getSpotlight(6);
            $this->loadPresentationRelations($spotlightItems);
            $spotlightItems = $spotlightItems->map(fn ($a) => $this->presentArtwork($a));
        }

        $mainCategories = $this->mainCategories();
        $contentType = null;
        $subcategories = $mainCategories;
        if (! $isAll) {
            $contentType = $resolution->contentType;
            $subcategories = $contentType
                ? $contentType->rootCategories()->orderBy('sort_order')->orderBy('name')->get()
                : collect();
        }

        if ($isAll) {
            $humanType = 'Artworks';
        } else {
            $humanType = $contentType?->name ?? ucfirst($resolvedTypeSlug);
        }

        $baseUrl = url('/explore/' . $resolvedTypeSlug);
        $seo = $this->paginationSeo($request, $baseUrl, $artworks);

        return view('gallery.index', [
            'gallery_type'          => $isAll ? 'browse' : 'content-type',
            'mainCategories'        => $mainCategories,
            'subcategories'         => $subcategories,
            'contentType'           => $contentType,
            'category'              => null,
            'artworks'              => $artworks,
            'spotlight'             => $spotlightItems,
            'current_sort'          => $sort,
            'sort_options'          => self::SORT_OPTIONS,
            'hero_title'            => $humanType,
            'hero_description'      => "Browse {$humanType} on Skinbase.",
            'breadcrumbs'           => collect([
                (object) ['name' => 'Explore', 'url' => '/explore'],
                (object) ['name' => $humanType, 'url' => "/explore/{$resolvedTypeSlug}"],
            ]),
            'page_title'            => "{$humanType} - Explore - Skinbase",
            'page_meta_description' => "Discover the best {$humanType} artworks on Skinbase. Browse trending, new and top-rated.",
            'page_meta_keywords'    => strtolower($resolvedTypeSlug) . ', explore, skinbase, artworks, wallpapers, skins, photography',
            'page_canonical'        => $seo['canonical'],
            'page_rel_prev'         => $seo['prev'],
            'page_rel_next'         => $seo['next'],
            'page_robots'           => 'index,follow',
        ]);
    }

    // ── /explore/:type/:mode ────────────────────────────────────────────

    public function byTypeMode(Request $request, string $type, string $mode)
    {
        $resolution = $this->contentTypeResolver->resolve($type, allowVirtual: true);

        if (! $resolution->found()) {
            abort(404);
        }

        if (! ($resolution->isVirtual && $resolution->virtualType === 'artworks')) {
            $query = $request->query();
            $query['sort'] = $this->normalizeSort((string) $mode);

            return redirect()->to($this->canonicalTypeUrl($request, strtolower((string) $resolution->contentType?->slug), $query), 301);
        }

        // Rewrite the sort via the URL segment and delegate
        $request->query->set('sort', $mode);
        return $this->byType($request, $type);
    }

    // ── /explore/best (Hall of Fame) ────────────────────────────────────

    /**
     * Hall of Fame: all-time highest-medal artworks, ranked by prestige.
     *
     * Algorithm:
     *   1. Primary:   score_total DESC   (all-time weighted medal score: gold×5 + silver×3 + bronze×1)
     *   2. Secondary: gold_count DESC    (prestige tiebreak — golds are rarer and more deliberate)
     *   3. Tertiary:  favorites_count DESC (overall community love)
     *
     * Only artworks published ≥ 30 days ago are eligible so freshly-viral
     * pieces don't crowd out genuine all-time standouts.
     *
     * Cache TTL is 1 hour — rankings shift slowly for the HoF.
     */
    public function hallOfFame(Request $request)
    {
        $perPage      = 24;
        $page         = max(1, (int) $request->query('page', 1));
        $minAge       = now()->subDays(30);
        $maturityUser = $request->user();

        $cacheVersion  = $this->cacheVersion();
        $viewerSegment = $maturityUser ? 'auth.' . $maturityUser->id : 'guest';
        $cacheKey      = "explore.hall-of-fame.v{$cacheVersion}.{$viewerSegment}.p{$page}";

        $paginator = Cache::remember($cacheKey, 3600, function () use ($perPage, $page, $minAge, $maturityUser): LengthAwarePaginator {
            $query = Artwork::query()
                ->public()
                ->published()
                ->tap(fn ($b) => $this->maturity->applyViewerFilter($b, $maturityUser))
                ->withoutMissingThumbnails()
                ->with([
                    'user:id,name,username',
                    'user.profile:user_id,avatar_hash',
                    'group:id,name,slug,headline,avatar_path,followers_count',
                    'categories:id,name,slug,content_type_id,sort_order',
                    'categories.contentType:id,name,slug',
                    'awardStat:artwork_id,gold_count,silver_count,bronze_count,score_total',
                    'stats:artwork_id,favorites',
                ])
                ->leftJoin('artwork_medal_stats as hof', 'hof.artwork_id', '=', 'artworks.id')
                ->leftJoin('artwork_stats as hof_stats', 'hof_stats.artwork_id', '=', 'artworks.id')
                ->select('artworks.*')
                // Must have at least one medal
                ->whereRaw('COALESCE(hof.score_total, 0) > 0')
                // Minimum 30-day age to exclude freshly-viral pieces
                ->where('artworks.published_at', '<=', $minAge)
                // Ranking: prestige-weighted medal score, then gold count, then favorites
                ->orderByRaw('COALESCE(hof.score_total, 0) DESC')
                ->orderByRaw('COALESCE(hof.gold_count, 0) DESC')
                ->orderByRaw('COALESCE(hof_stats.favorites, 0) DESC');

            return $query->paginate($perPage, ['artworks.*'], 'page', $page)
                ->withPath(url('/explore/best'));
        });

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

        $mainCategories = $this->mainCategories();
        $seo            = $this->paginationSeo($request, url('/explore/best'), $paginator);

        return view('gallery.index', [
            'gallery_type'          => 'browse',
            'gallery_nav_section'   => 'artworks',
            'mainCategories'        => $mainCategories,
            'subcategories'         => $mainCategories,
            'contentType'           => null,
            'category'              => null,
            'artworks'              => $paginator,
            'spotlight'             => collect(),
            'hide_rank_tabs'        => true,
            'current_sort'          => 'top-rated',
            'sort_options'          => [],
            'hero_title'            => 'Hall of Fame',
            'hero_description'      => 'All-time medal standouts ranked by prestige — the artworks the community has honoured most across the years.',
            'breadcrumbs'           => collect([
                (object) ['name' => 'Explore', 'url' => '/explore'],
                (object) ['name' => 'Hall of Fame', 'url' => '/explore/best'],
            ]),
            'page_title'            => 'Hall of Fame — All-Time Best Artworks - Skinbase',
            'page_meta_description' => 'The highest-medal artworks of all time on Skinbase, ranked by gold, silver and bronze prestige.',
            'page_meta_keywords'    => 'hall of fame, best artworks, top rated, medals, skinbase',
            'page_canonical'        => $seo['canonical'],
            'page_rel_prev'         => $seo['prev'],
            'page_rel_next'         => $seo['next'],
            'page_robots'           => 'index,follow',
        ]);
    }

    // ── Helpers ──────────────────────────────────────────────────────────

    private function mainCategories(): Collection
    {
        $categories = $this->contentTypeResolver
            ->toolbarContentTypes()
            ->map(fn ($ct) => (object) [
                'name' => $ct->name,
                'slug' => $ct->slug,
                'url'  => '/' . strtolower($ct->slug),
            ]);

        return $categories->push((object) [
            'name' => 'Members',
            'slug' => 'members',
            'url'  => '/members',
        ]);
    }

    private function resolveSort(Request $request): string
    {
        $s = $this->normalizeSort((string) $request->query('sort', 'trending'));
        return array_key_exists($s, self::SORT_MAP) ? $s : 'trending';
    }

    private function normalizeSort(string $sort): string
    {
        $sort = strtolower($sort);
        return self::SORT_ALIASES[$sort] ?? $sort;
    }

    private function canonicalTypeUrl(Request $request, string $type, ?array $query = null): string
    {
        $query = $query ?? $request->query();

        if (isset($query['sort'])) {
            $query['sort'] = $this->normalizeSort((string) $query['sort']);
            if ($query['sort'] === 'trending') {
                unset($query['sort']);
            }
        }

        return url('/' . $type) . ($query ? ('?' . http_build_query($query)) : '');
    }

    private function resolvePerPage(Request $request): int
    {
        $v = (int) ($request->query('per_page') ?: $request->query('limit') ?: 24);
        return max(12, min($v, 80));
    }

    private function requestCacheSuffix(Request $request): string
    {
        $query = $request->query();
        unset($query['grid'], $query['page']);
        ksort($query);

        return md5(json_encode($query, JSON_THROW_ON_ERROR));
    }

    private function cacheVersion(): int
    {
        return max(1, (int) Cache::get('explore.cache.version', 1));
    }

    private function buildExploreFilterExpression(Request $request, ?string $contentType = null): string
    {
        $filterParts = [
            'is_public = true',
            'is_approved = true',
        ];

        if ($contentType !== null && $contentType !== '') {
            $quoted = addslashes($contentType);
            $filterParts[] = '(content_type = "' . $quoted . '" OR content_types = "' . $quoted . '")';
        }

        $orientation = strtolower(trim((string) $request->query('orientation', '')));
        if (in_array($orientation, ['landscape', 'portrait', 'square'], true)) {
            $filterParts[] = 'orientation = "' . addslashes($orientation) . '"';
        }

        $resolution = $this->resolutionFilterValue((string) $request->query('resolution', ''));
        if ($resolution !== null) {
            $filterParts[] = 'resolution = "' . addslashes($resolution) . '"';
        }

        $dateFrom = $this->normalizeDateQuery((string) $request->query('date_from', ''));
        if ($dateFrom !== null) {
            $filterParts[] = 'created_at >= "' . $dateFrom . '"';
        }

        $dateTo = $this->normalizeDateQuery((string) $request->query('date_to', ''));
        if ($dateTo !== null) {
            $filterParts[] = 'created_at <= "' . $dateTo . '"';
        }

        $authorFilter = $this->authorFilterExpression((string) $request->query('author', ''));
        if ($authorFilter !== null) {
            $filterParts[] = $authorFilter;
        }

        return implode(' AND ', $filterParts);
    }

    private function resolutionFilterValue(string $resolution): ?string
    {
        return match (strtolower(trim($resolution))) {
            'hd' => '1280x720',
            'fhd' => '1920x1080',
            '2k' => '2560x1440',
            '4k' => '3840x2160',
            default => null,
        };
    }

    private function normalizeDateQuery(string $value): ?string
    {
        $value = trim($value);

        if (! preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
            return null;
        }

        return $value;
    }

    private function authorFilterExpression(string $author): ?string
    {
        $author = trim($author);

        if ($author === '') {
            return null;
        }

        $userIds = User::query()
            ->where(function ($query) use ($author): void {
                $query->where('username', 'like', '%' . $author . '%')
                    ->orWhere('name', 'like', '%' . $author . '%');
            })
            ->limit(20)
            ->pluck('id');

        $groupIds = Group::query()
            ->where(function ($query) use ($author): void {
                $query->where('name', 'like', '%' . $author . '%')
                    ->orWhere('slug', 'like', '%' . $author . '%');
            })
            ->limit(20)
            ->pluck('id');

        $clauses = [];

        foreach ($userIds as $userId) {
            $clauses[] = '(author_id = ' . (int) $userId . ' AND published_as_type = "user")';
        }

        foreach ($groupIds as $groupId) {
            $clauses[] = '(author_id = ' . (int) $groupId . ' AND published_as_type = "group")';
        }

        if ($clauses === []) {
            return 'id = 0';
        }

        return '(' . implode(' OR ', $clauses) . ')';
    }

    private function filterBrowsableArtworks(AbstractPaginator $paginator): AbstractPaginator
    {
        $paginator->setCollection(
            $paginator->getCollection()
                ->filter(fn ($artwork) => $artwork instanceof Artwork
                    && $artwork->deleted_at === null
                    && (bool) $artwork->is_public
                    && (bool) $artwork->is_approved
                    && $artwork->published_at !== null)
                ->values()
        );

        return $paginator;
    }

    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()
            : \App\Support\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());
    }

    private function loadPresentationRelations(mixed $artworks): void
    {
        if (is_object($artworks) && method_exists($artworks, 'loadMissing')) {
            $artworks->loadMissing(['user.profile', 'group', 'categories.contentType']);
        }
    }

    private function paginationSeo(Request $request, string $base, mixed $paginator): array
    {
        $q = $request->query();
        unset($q['grid']);
        if (($q['page'] ?? null) !== null && (int) $q['page'] <= 1) {
            unset($q['page']);
        }
        $canonical = $base . ($q ? '?' . http_build_query($q) : '');

        $prev = null;
        $next = null;
        if ($paginator instanceof AbstractPaginator || $paginator instanceof AbstractCursorPaginator) {
            $prev = $paginator->previousPageUrl();
            $next = $paginator->nextPageUrl();
        }

        return compact('canonical', 'prev', 'next');
    }
}
