<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Artwork;
use App\Models\Tag;
use App\Services\EarlyGrowth\AdaptiveTimeWindow;
use App\Services\Maturity\ArtworkMaturityService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\LengthAwarePaginator as PaginationLengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;

/**
 * High-level search API powered by Meilisearch via Laravel Scout.
 *
 * No Meili calls in controllers — always go through this service.
 */
final class ArtworkSearchService
{
    private const BASE_FILTER = 'is_public = true AND is_approved = true';
    private const CACHE_TTL   = 300; // 5 minutes
    private const TAG_SORTS   = ['popular', 'likes', 'latest', 'downloads'];
    private const SEARCH_CANDIDATE_POOL_MULTIPLIER = 4;
    private const SEARCH_CANDIDATE_POOL_MAX = 240;

    public function __construct(
        private readonly AdaptiveTimeWindow $timeWindow,
        private readonly ArtworkMaturityService $maturity,
    ) {}

    /**
     * Full-text search with optional filters.
     *
     * Supported $filters keys:
     *   tags        array<string>  — tag slugs (AND match)
     *   category    string
     *   orientation string         — landscape | portrait | square
     *   resolution  string         — e.g. "1920x1080"
     *   author_id   int
     *   sort        string         — created_at|downloads|likes|views (suffix :asc or :desc)
     */
    public function search(string $q, array $filters = [], int $perPage = 24): LengthAwarePaginator
    {
        $filterParts = [self::BASE_FILTER];
        $sort        = [];

        if (! empty($filters['tags'])) {
            foreach ((array) $filters['tags'] as $tag) {
                $filterParts[] = 'tags = "' . addslashes((string) $tag) . '"';
            }
        }

        if (! empty($filters['category'])) {
            $filterParts[] = 'category = "' . addslashes((string) $filters['category']) . '"';
        }

        if (! empty($filters['orientation'])) {
            $filterParts[] = 'orientation = "' . addslashes((string) $filters['orientation']) . '"';
        }

        if (! empty($filters['resolution'])) {
            $filterParts[] = 'resolution = "' . addslashes((string) $filters['resolution']) . '"';
        }

        if (! empty($filters['author_id'])) {
            $filterParts[] = 'author_id = ' . (int) $filters['author_id'];
        }

        if (! empty($filters['sort'])) {
            [$field, $dir] = $this->parseSort((string) $filters['sort']);
            if ($field) {
                $sort[] = $field . ':' . $dir;
            }
        }

        $options = ['filter' => implode(' AND ', $filterParts)];
        if ($sort !== []) {
            $options['sort'] = $sort;
        }

        $options = $this->viewerAwareOptions($options);

        return Artwork::search($q ?: '')
            ->options($options)
            ->paginate($perPage);
    }

    public function searchWithThumbnailPreference(array $options, int $perPage, bool $excludeMissing = false, ?int $page = null): LengthAwarePaginator
    {
        $page = max(1, $page ?? (int) request()->get('page', 1));
        $candidateCount = $this->determineSearchCandidatePoolSize($perPage, $page);
        $results = Artwork::search('')
            ->options($this->viewerAwareOptions($options))
            ->paginate($candidateCount, 'page', 1);

        $ordered = $this->rerankSearchCollectionByThumbnailHealth($results->getCollection(), $excludeMissing);
        $offset = max(0, ($page - 1) * $perPage);
        $slice = $ordered->slice($offset, $perPage)->values();

        return new PaginationLengthAwarePaginator(
            $slice->all(),
            (int) $results->total(),
            $perPage,
            $page,
            [
                'path' => request()->url(),
                'query' => request()->query(),
                'pageName' => 'page',
            ]
        );
    }

    /**
     * Load artworks for a tag page, sorted by views + likes descending.
     */
    public function byTag(string $slug, int $perPage = 24, string $sort = 'popular'): LengthAwarePaginator
    {
        $tag = Tag::where('slug', $slug)->first();
        if (! $tag) {
            return $this->emptyPaginator($perPage);
        }

        $sort = in_array($sort, self::TAG_SORTS, true) ? $sort : 'popular';
        $cacheKey = "search.tag.{$slug}.{$sort}.{$perPage}.{$this->viewerCacheSegment()}.page." . request()->get('page', 1);

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($tag, $perPage, $sort) {
            $query = Artwork::query()
                ->public()
                ->published()
                ->tap(fn ($builder) => $this->maturity->applyViewerFilter($builder, request()->user()))
            ->whereHas('tags', fn ($tagQuery) => $tagQuery->where('tags.id', $tag->id))
                ->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id')
                ->select('artworks.*')
                ->with(['user.profile', 'categories.contentType']);

            match ($sort) {
                'likes' => $query
                    ->orderByRaw('COALESCE(artwork_stats.favorites, 0) DESC')
                    ->orderByRaw('COALESCE(artwork_stats.views, 0) DESC')
                    ->orderByDesc('artworks.published_at'),
                'latest' => $query
                    ->orderByDesc('artworks.published_at')
                    ->orderByDesc('artworks.id'),
                'downloads' => $query
                    ->orderByRaw('COALESCE(artwork_stats.downloads, 0) DESC')
                    ->orderByRaw('COALESCE(artwork_stats.views, 0) DESC')
                    ->orderByDesc('artworks.published_at'),
                default => $query
                    ->orderByRaw('COALESCE(artwork_stats.views, 0) DESC')
                    ->orderByRaw('COALESCE(artwork_stats.favorites, 0) DESC')
                    ->orderByDesc('artworks.published_at'),
            };

            return $query
                ->paginate($perPage)
                ->withQueryString();
        });
    }

    /**
     * Load artworks for a category, sorted by created_at desc.
     */
    public function byCategory(string $cat, int $perPage = 24, array $filters = []): LengthAwarePaginator
    {
        $page = (int) request()->get('page', 1);
        $cacheKey = "search.cat.catalog-visible.v2.{$cat}.{$this->viewerCacheSegment()}.page." . $page;

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($cat, $perPage, $page) {
            return $this->searchWithThumbnailPreference([
                'filter' => self::BASE_FILTER . ' AND category = "' . addslashes($cat) . '"',
                'sort'   => ['created_at:desc'],
            ], $perPage, false, $page);
        });
    }

    // ── Category / Content-Type page sorts ────────────────────────────────────

    /**
     * Meilisearch sort fields per alias.
     * Used by categoryPageSort() and contentTypePageSort().
     */
    private const CATEGORY_SORT_FIELDS = [
        'trending'   => ['trending_score_24h:desc', 'published_at_ts:desc'],
        'fresh'      => ['published_at_ts:desc'],
        'top-rated'  => ['awards_received_count:desc', 'favorites_count:desc'],
        'favorited'  => ['favorites_count:desc', 'trending_score_24h:desc'],
        'downloaded' => ['downloads_count:desc', 'trending_score_24h:desc'],
        'oldest'     => ['published_at_ts:asc'],
    ];

    /** Cache TTL (seconds) per sort alias for category pages. */
    private const CATEGORY_SORT_TTL = [
        'trending'   => 300,   // 5 min
        'fresh'      => 120,   // 2 min
        'top-rated'  => 600,   // 10 min
        'favorited'  => 300,
        'downloaded' => 300,
        'oldest'     => 600,
    ];

    /**
     * Artworks for a single category page, sorted via Meilisearch.
     * Default sort: trending (trending_score_24h:desc).
     *
     * Cache key pattern: category.{slug}.{sort}.{page}
     * TTL varies by sort (see spec: 5/2/10 min).
     */
    public function categoryPageSort(string $categorySlug, string $sort = 'trending', int $perPage = 24): LengthAwarePaginator
    {
        $sort    = array_key_exists($sort, self::CATEGORY_SORT_FIELDS) ? $sort : 'trending';
        $page    = (int) request()->get('page', 1);
        $ttl     = self::CATEGORY_SORT_TTL[$sort] ?? self::CACHE_TTL;
        $cacheKey = "category.catalog-visible.v2.{$categorySlug}.{$sort}.{$this->viewerCacheSegment()}.{$page}";

        return Cache::remember($cacheKey, $ttl, function () use ($categorySlug, $sort, $perPage) {
            return $this->searchWithThumbnailPreference([
                'filter' => self::BASE_FILTER . ' AND category = "' . addslashes($categorySlug) . '"',
                'sort'   => self::CATEGORY_SORT_FIELDS[$sort],
            ], $perPage, false, (int) request()->get('page', 1));
        });
    }

    /**
     * Artworks for a content-type root page, sorted via Meilisearch.
     * Default sort: trending.
     *
     * Cache key pattern: content_type.{slug}.{sort}.{page}
     */
    public function contentTypePageSort(string $contentTypeSlug, string $sort = 'trending', int $perPage = 24): LengthAwarePaginator
    {
        $sort    = array_key_exists($sort, self::CATEGORY_SORT_FIELDS) ? $sort : 'trending';
        $page    = (int) request()->get('page', 1);
        $ttl     = self::CATEGORY_SORT_TTL[$sort] ?? self::CACHE_TTL;
        $cacheKey = "content_type.catalog-visible.v2.{$contentTypeSlug}.{$sort}.{$this->viewerCacheSegment()}.{$page}";

        return Cache::remember($cacheKey, $ttl, function () use ($contentTypeSlug, $sort, $perPage) {
            return $this->searchWithThumbnailPreference([
                'filter' => self::BASE_FILTER . ' AND content_type = "' . addslashes($contentTypeSlug) . '"',
                'sort'   => self::CATEGORY_SORT_FIELDS[$sort],
            ], $perPage, false, (int) request()->get('page', 1));
        });
    }

    // -------------------------------------------------------------------------

    /**
     * Related artworks: same tags, different artwork, ranked by views + likes.
     * Limit 12.
     */
    public function related(Artwork $artwork, int $limit = 12): LengthAwarePaginator
    {
        $tags = $artwork->tags()->pluck('tags.slug')->values()->all();

        if ($tags === []) {
            return $this->popular($limit);
        }

        $cacheKey = "search.related.{$artwork->id}.{$this->viewerCacheSegment()}";

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($artwork, $tags, $limit) {
            $tagFilters = implode(' OR ', array_map(
                fn ($t) => 'tags = "' . addslashes($t) . '"',
                $tags
            ));

            return Artwork::search('')
                ->options($this->viewerAwareOptions([
                    'filter' => self::BASE_FILTER . ' AND id != ' . $artwork->id . ' AND (' . $tagFilters . ')',
                    'sort'   => ['views:desc', 'likes:desc'],
                ]))
                ->paginate($limit);
        });
    }

    /**
     * Most popular artworks by views.
     */
    public function popular(int $perPage = 24): LengthAwarePaginator
    {
        return Cache::remember('search.popular.' . $this->viewerCacheSegment() . '.page.' . request()->get('page', 1), self::CACHE_TTL, function () use ($perPage) {
            return Artwork::search('')
                ->options($this->viewerAwareOptions([
                    'filter' => self::BASE_FILTER,
                    'sort'   => ['views:desc', 'likes:desc'],
                ]))
                ->paginate($perPage);
        });
    }

    /**
     * Most recent artworks by publish timestamp.
     */
    public function recent(int $perPage = 24): LengthAwarePaginator
    {
        return Cache::remember('search.recent.' . $this->viewerCacheSegment() . '.page.' . request()->get('page', 1), self::CACHE_TTL, function () use ($perPage) {
            return Artwork::search('')
                ->options($this->viewerAwareOptions([
                    'filter' => self::BASE_FILTER,
                    'sort'   => ['published_at_ts:desc'],
                ]))
                ->paginate($perPage);
        });
    }

    // ── Discover section helpers ───────────────────────────────────────────────

    /**
     * Trending: sorted by Ranking Engine V2 `ranking_score` (recalculated every 30 min).
     *
     * Spec §6: Uses ranking_score, limits to last 30 days,
     * highlights high-velocity artworks via engagement_velocity tiebreaker.
     */
    public function discoverTrending(int $perPage = 24): LengthAwarePaginator
    {
        $page        = (int) request()->get('page', 1);
        $windowDays  = $this->timeWindow->getTrendingWindowDays(30);
        $cutoff      = now()->subDays($windowDays)->toDateString();
        // Include window in cache key so adaptive expansions surface immediately
        $cacheKey = "discover.trending.{$windowDays}d.{$this->viewerCacheSegment()}.{$page}";

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($perPage, $cutoff) {
            return $this->searchWithThumbnailPreference([
                'filter' => self::BASE_FILTER . ' AND created_at >= "' . $cutoff . '"',
                'sort'   => ['ranking_score:desc', 'engagement_velocity:desc', 'views:desc'],
            ], $perPage);
        });
    }

    /**
     * Rising: sorted by heat_score (recalculated every 15 min).
     *
     * Surfaces artworks with rapid recent engagement growth.
     * Restricts to last 30 days, sorted by heat_score DESC.
     */
    public function discoverRising(int $perPage = 24): LengthAwarePaginator
    {
        $page        = (int) request()->get('page', 1);
        $windowDays  = $this->timeWindow->getTrendingWindowDays(30);
        $cutoff      = now()->subDays($windowDays)->toDateString();
        $cacheKey    = "discover.rising.{$windowDays}d.{$this->viewerCacheSegment()}.{$page}";

        return Cache::remember($cacheKey, 120, function () use ($perPage, $cutoff) {
            return $this->searchWithThumbnailPreference([
                'filter' => self::BASE_FILTER . ' AND created_at >= "' . $cutoff . '"',
                'sort'   => ['heat_score:desc', 'engagement_velocity:desc', 'published_at_ts:desc'],
            ], $perPage);
        });
    }

    /**
     * Fresh: newest uploads first.
     */
    public function discoverFresh(int $perPage = 24): LengthAwarePaginator
    {
        $page = (int) request()->get('page', 1);
        return Cache::remember("discover.fresh.{$this->viewerCacheSegment()}.{$page}", self::CACHE_TTL, function () use ($perPage) {
            return $this->searchWithThumbnailPreference([
                'filter' => self::BASE_FILTER,
                'sort'   => ['published_at_ts:desc'],
            ], $perPage);
        });
    }

    /**
     * Top rated: highest number of favourites/likes.
     */
    public function discoverTopRated(int $perPage = 24): LengthAwarePaginator
    {
        $page = (int) request()->get('page', 1);
        return Cache::remember("discover.top-rated.{$this->viewerCacheSegment()}.{$page}", self::CACHE_TTL, function () use ($perPage) {
            return $this->searchWithThumbnailPreference([
                'filter' => self::BASE_FILTER,
                'sort'   => ['likes:desc', 'views:desc'],
            ], $perPage);
        });
    }

    /**
     * Most downloaded: highest download count.
     */
    public function discoverMostDownloaded(int $perPage = 24): LengthAwarePaginator
    {
        $page = (int) request()->get('page', 1);
        return Cache::remember("discover.most-downloaded.{$this->viewerCacheSegment()}.{$page}", self::CACHE_TTL, function () use ($perPage) {
            return $this->searchWithThumbnailPreference([
                'filter' => self::BASE_FILTER,
                'sort'   => ['downloads:desc', 'views:desc'],
            ], $perPage);
        });
    }

    /**
     * Artworks matching any of the given tag slugs, sorted by trending score.
     * Used for personalized "Because you like {tags}" homepage section.
     *
     * @param string[] $tagSlugs
     */
    public function discoverByTags(array $tagSlugs, int $limit = 12): LengthAwarePaginator
    {
        if (empty($tagSlugs)) {
            return $this->popular($limit);
        }

        $tagFilter = implode(' OR ', array_map(
            fn (string $t): string => 'tags = "' . addslashes($t) . '"',
            array_slice($tagSlugs, 0, 5)
        ));

        $cacheKey = 'discover.by-tags.' . $this->viewerCacheSegment() . '.' . md5(implode(',', $tagSlugs));

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($tagFilter, $limit) {
            return $this->searchWithThumbnailPreference([
                'filter' => self::BASE_FILTER . ' AND (' . $tagFilter . ')',
                'sort'   => ['trending_score_7d:desc', 'likes:desc'],
            ], $limit, true, 1);
        });
    }

    private function viewerAwareOptions(array $options): array
    {
        $options['filter'] = $this->maturity->appendSearchFilter((string) ($options['filter'] ?? self::BASE_FILTER), request()->user());

        return $options;
    }

    private function viewerCacheSegment(): string
    {
        return 'visibility-' . $this->maturity->viewerPreferences(request()->user())['visibility'];
    }

    /**
    * Fresh artworks in given categories, sorted by publish timestamp desc.
     * Used for personalized "Fresh in your favourite categories" section.
     *
     * @param string[] $categorySlugs
     */
    public function discoverByCategories(array $categorySlugs, int $limit = 12): LengthAwarePaginator
    {
        if (empty($categorySlugs)) {
            return $this->recent($limit);
        }

        $catFilter = implode(' OR ', array_map(
            fn (string $c): string => 'category = "' . addslashes($c) . '"',
            array_slice($categorySlugs, 0, 3)
        ));

        $cacheKey = 'discover.by-cats.' . $this->viewerCacheSegment() . '.' . md5(implode(',', $categorySlugs));

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($catFilter, $limit) {
            return $this->searchWithThumbnailPreference([
                'filter' => self::BASE_FILTER . ' AND (' . $catFilter . ')',
                'sort'   => ['published_at_ts:desc'],
            ], $limit, true, 1);
        });
    }

    // -------------------------------------------------------------------------

    private function parseSort(string $sort): array
    {
        $allowed = ['created_at', 'downloads', 'likes', 'views'];
        $parts   = explode(':', $sort, 2);
        $field   = $parts[0] ?? '';
        $dir     = strtolower($parts[1] ?? 'desc') === 'asc' ? 'asc' : 'desc';

        return in_array($field, $allowed, true) ? [$field, $dir] : [null, 'desc'];
    }

    private function rerankSearchCollectionByThumbnailHealth(Collection $items, bool $excludeMissing): Collection
    {
        if ($items->isEmpty()) {
            return $items;
        }

        $ids = $items
            ->pluck('id')
            ->filter(fn ($id) => is_numeric($id) && (int) $id > 0)
            ->map(fn ($id) => (int) $id)
            ->values();

        if ($ids->isEmpty()) {
            return $items->values();
        }

        $missingIds = Artwork::query()
            ->whereIn('id', $ids)
            ->where('has_missing_thumbnails', true)
            ->pluck('id')
            ->map(fn ($id) => (int) $id)
            ->flip();

        if ($missingIds->isEmpty()) {
            return $items->values();
        }

        $healthy = $items->reject(fn ($item) => $missingIds->has((int) ($item->id ?? 0)));

        if ($excludeMissing) {
            return $healthy->values();
        }

        return $healthy
            ->concat($items->filter(fn ($item) => $missingIds->has((int) ($item->id ?? 0))))
            ->values();
    }

    private function determineSearchCandidatePoolSize(int $perPage, int $page): int
    {
        return min(
            self::SEARCH_CANDIDATE_POOL_MAX,
            max($perPage, $perPage * max(self::SEARCH_CANDIDATE_POOL_MULTIPLIER, $page + 2))
        );
    }

    private function emptyPaginator(int $perPage): LengthAwarePaginator
    {
        return new \Illuminate\Pagination\LengthAwarePaginator([], 0, $perPage);
    }
}
