<?php

declare(strict_types=1);

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Http\Resources\ArtworkListResource;
use App\Services\ArtworkSearchService;
use App\Services\GroupDiscoveryService;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\View\View;
use cPad\Plugins\News\Models\NewsArticle;

final class SearchController extends Controller
{
    private const ALLOWED_SORTS = ['latest', 'popular', 'likes', 'downloads'];

    public function __construct(
        private readonly ArtworkSearchService $search,
        private readonly GroupDiscoveryService $groups,
    ) {}

    public function index(Request $request): View|RedirectResponse
    {
        $canonicalQuery = $this->canonicalQueryParameters($request);
        $canonicalUrl = $this->canonicalSearchUrl($request, $canonicalQuery);

        if ($request->fullUrl() !== $canonicalUrl) {
            return redirect()->to($canonicalUrl, 301);
        }

        $q = (string) ($canonicalQuery['q'] ?? '');
        $sort = (string) ($canonicalQuery['sort'] ?? 'latest');
        $hasQuery = $q !== '';

        $sortMap = [
            'popular'   => 'views:desc',
            'likes'     => 'likes:desc',
            'latest'    => 'created_at:desc',
            'downloads' => 'downloads:desc',
        ];

        $artworks = $hasQuery
            ? $this->search->search($q, [
                'sort' => ($sortMap[$sort] ?? 'created_at:desc'),
            ])
            : $this->search->popular(24);

        $groups = $hasQuery
            ? $this->groups->searchCards($q, $request->user(), 6)
            : $this->groups->surfaceCards($request->user(), 'featured', 4);

        $news = $hasQuery
            ? NewsArticle::query()
                ->with(['author:id,username,name', 'category:id,name,slug'])
                ->published()
                ->where(function ($builder) use ($q): void {
                    $builder->where('title', 'like', '%' . $q . '%')
                        ->orWhere('excerpt', 'like', '%' . $q . '%')
                        ->orWhere('content', 'like', '%' . $q . '%')
                        ->orWhere('meta_title', 'like', '%' . $q . '%');
                })
                ->editorialOrder()
                ->limit(4)
                ->get()
            : collect();

        $groupResults = collect($groups ?? []);
        $newsResults = collect($news ?? []);
        $resultCount = method_exists($artworks, 'total') ? (int) $artworks->total() : 0;
        $groupResultCount = $groupResults->count();
        $newsResultCount = $newsResults->count();
        $hasAnyResults = $resultCount > 0 || $groupResultCount > 0 || $newsResultCount > 0;

        $galleryItems = method_exists($artworks, 'getCollection')
            ? $artworks->getCollection()
            : new EloquentCollection(collect($artworks)->all());

        $galleryItems->loadMissing(['user.profile', 'group', 'categories.contentType']);

        $galleryArtworks = $galleryItems
            ->map(fn ($artwork) => (new ArtworkListResource($artwork))->resolve($request))
            ->values()
            ->all();

        $galleryNextPageUrl = method_exists($artworks, 'nextPageUrl') ? $artworks->nextPageUrl() : null;

        return view('search.index', [
            'q'                     => $q,
            'hasQuery'              => $hasQuery,
            'sort'                  => $sort,
            'groups'                => $groups,
            'groupResults'          => $groupResults,
            'groupResultCount'      => $groupResultCount,
            'artworks'              => $artworks,
            'resultCount'           => $resultCount,
            'news'                  => $news,
            'newsResults'           => $newsResults,
            'newsResultCount'       => $newsResultCount,
            'hasAnyResults'         => $hasAnyResults,
            'galleryArtworks'       => $galleryArtworks,
            'galleryNextPageUrl'    => $galleryNextPageUrl,
            'page_title'            => $hasQuery ? 'Search: ' . $q . ' — Skinbase' : 'Search — Skinbase',
            'page_meta_description' => 'Search Skinbase for artworks, creators, groups, photography, wallpapers and skins.',
            'page_robots'           => 'noindex,follow',
        ]);
    }

    /**
     * @return array<string, int|string>
     */
    private function canonicalQueryParameters(Request $request): array
    {
        $q = $this->normalizeSearchQuery($request->query('q', ''));

        if ($q === '') {
            return [];
        }

        $params = ['q' => $q];
        $sort = $this->normalizeSort($request->query('sort', 'latest'));
        $page = $this->normalizePage($request->query('page', 1));

        if ($sort !== 'latest') {
            $params['sort'] = $sort;
        }

        if ($page > 1) {
            $params['page'] = $page;
        }

        return $params;
    }

    /**
     * @param  array<string, int|string>  $params
     */
    private function canonicalSearchUrl(Request $request, array $params): string
    {
        $query = Arr::query($params);

        return $query === '' ? $request->url() : $request->url() . '?' . $query;
    }

    private function normalizeSearchQuery(mixed $value): string
    {
        $query = html_entity_decode($this->firstScalarValue($value), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $query = preg_replace('/(?:\?|&)(?:amp;)?(?:page|sort|filter|group|id|txtfilter|q)=.*$/i', '', $query) ?? $query;
        $query = preg_replace('/\s+/u', ' ', $query) ?? $query;

        return trim($query, " \t\n\r\0\x0B?&");
    }

    private function normalizeSort(mixed $value): string
    {
        $sort = strtolower($this->firstScalarValue($value));
        $sort = preg_replace('/(?:\?|&).*/', '', $sort) ?? $sort;

        return in_array($sort, self::ALLOWED_SORTS, true) ? $sort : 'latest';
    }

    private function normalizePage(mixed $value): int
    {
        $page = $this->firstScalarValue($value);

        if (preg_match('/\d+/', $page, $matches) !== 1) {
            return 1;
        }

        return max(1, (int) $matches[0]);
    }

    private function firstScalarValue(mixed $value): string
    {
        if (is_array($value)) {
            $value = reset($value);
        }

        if (! is_scalar($value) && $value !== null) {
            return '';
        }

        return trim((string) $value);
    }
}
