<?php
namespace App\Services;

use App\Models\Artwork;
use App\Models\Category;
use App\Models\ContentType;
use App\Models\User;
use App\Services\ContentTypes\ContentTypeSlugResolver;
use App\Services\Maturity\ArtworkMaturityService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\Pagination\CursorPaginator;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;

/**
 * ArtworkService
 *
 * Business logic for retrieving artworks. Controllers should remain thin and
 * delegate to this service. This service never returns JSON or accesses
 * the request() helper directly.
 */
class ArtworkService
{
    protected int $cacheTtl = 3600; // seconds

    public function __construct(
        private readonly ContentTypeSlugResolver $contentTypeResolver,
        private readonly ArtworkMaturityService $maturity,
    )
    {
    }

    /**
     * Relations used by the featured artwork surfaces.
     *
     * @return array<int|string, mixed>
     */
    private function featuredRelations(): array
    {
        return [
            'user:id,name,username',
            'user.profile:user_id,avatar_hash',
            'awardStat:artwork_id,gold_count,silver_count,bronze_count,score_total,score_7d,score_30d,last_medaled_at,updated_at',
            'categories' => function ($q) {
                $q->select('categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order');
            },
        ];
    }

    /**
     * Lightweight relations needed to render browse/list cards.
     *
     * @return array<int|string, mixed>
     */
    private function browseRelations(): array
    {
        return [
            'user:id,name,username',
            'user.profile:user_id,avatar_hash',
            'group:id,name,slug,avatar_path',
            'categories' => function ($q) {
                $q->select('categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order')
                    ->with(['parent:id,parent_id,content_type_id,name,slug', 'contentType:id,slug,name']);
            },
        ];
    }

    /**
     * Shared browse query used by /browse, content-type pages, and category pages.
     */
    private function browseQuery(string $sort = 'latest'): Builder
    {
        $query = Artwork::public()
            ->published()
            ->tap(fn ($builder) => $this->maturity->applyViewerFilter($builder, request()->user()))
            ->with($this->browseRelations());

        $normalizedSort = strtolower(trim($sort));
        if ($normalizedSort === 'oldest') {
            return $query->orderBy('published_at', 'asc');
        }

        return $query->orderByDesc('published_at');
    }

    /**
     * Fetch a single public artwork by slug.
     * Applies visibility rules (public + approved + not-deleted).
     *
     * @param string $slug
     * @return Artwork
     * @throws ModelNotFoundException
     */
    public function getPublicArtworkBySlug(string $slug): Artwork
    {
        $key = 'artwork:' . $slug;

        $artwork = Cache::remember($key, $this->cacheTtl, function () use ($slug) {
            $a = Artwork::where('slug', $slug)
                ->public()
                ->published()
                ->first();

            if (! $a) {
                return null;
            }

            // Load lightweight relations for presentation; do NOT eager-load stats here.
            $a->load(['translations', 'categories']);

            return $a;
        });

        if (! $artwork) {
            $e = new ModelNotFoundException();
            $e->setModel(Artwork::class, [$slug]);
            throw $e;
        }

        return $artwork;
    }

    /**
     * Clear artwork cache by model instance.
     */
    public function clearArtworkCache(Artwork $artwork): void
    {
        $this->clearArtworkCacheBySlug($artwork->slug);
    }

    /**
     * Clear artwork cache by slug.
     */
    public function clearArtworkCacheBySlug(string $slug): void
    {
        Cache::forget('artwork:' . $slug);
    }

    /**
     * Get artworks for a given category, applying visibility rules and cursor pagination.
     * Returns a CursorPaginator so controllers/resources can render paginated feeds.
     *
     * @param Category $category
     * @param int $perPage
     * @return CursorPaginator
     */
    public function getCategoryArtworks(Category $category, int $perPage = 24): CursorPaginator
    {
        $query = Artwork::public()->published()
            ->tap(fn ($builder) => $this->maturity->applyViewerFilter($builder, request()->user()))
            ->with($this->browseRelations())
            ->whereHas('categories', function ($q) use ($category) {
                $q->where('categories.id', $category->id);
            })
            ->orderByDesc('published_at');

        // Important: do NOT eager-load artwork_stats in listings
        return $query->cursorPaginate($perPage);
    }

    /**
     * Return the latest public artworks up to $limit.
     *
     * @param int $limit
     * @return \Illuminate\Support\Collection|EloquentCollection
     */
    public function getLatestArtworks(int $limit = 10): Collection
    {
        return Artwork::public()->published()
            ->tap(fn ($builder) => $this->maturity->applyViewerFilter($builder, request()->user()))
            ->orderByDesc('published_at')
            ->limit($limit)
            ->get();
    }

    /**
     * Browse all public, approved, published artworks with pagination.
     * Uses new authoritative tables only (no legacy joins) and eager-loads
     * lightweight relations needed for presentation.
     */
    public function browsePublicArtworks(int $perPage = 24, string $sort = 'latest'): CursorPaginator
    {
        $query = $this->browseQuery($sort);

        // Use cursor pagination for high-load browse feeds (SEO handled via canonical URLs).
        return $query->cursorPaginate($perPage);
    }

    /**
     * Browse artworks scoped to a content type slug using keyset pagination.
     * Applies public + approved + published filters.
     */
    public function getArtworksByContentType(string $slug, int $perPage, string $sort = 'latest'): CursorPaginator
    {
        $contentType = $this->resolveContentTypeOrFail($slug);

        $query = $this->browseQuery($sort)
            ->whereHas('categories', function ($q) use ($contentType) {
                $q->where('categories.content_type_id', $contentType->id);
            });

        return $query->cursorPaginate($perPage);
    }

    /**
     * Browse artworks for a category path (content type slug + nested category slugs).
     * Uses slug-only resolution and keyset pagination.
     *
     * @param array<int, string> $slugs
     */
    public function getArtworksByCategoryPath(array $slugs, int $perPage, string $sort = 'latest'): CursorPaginator
    {
        if (empty($slugs)) {
            $e = new ModelNotFoundException();
            $e->setModel(Category::class);
            throw $e;
        }

        $parts = array_values(array_map('strtolower', $slugs));
        $contentTypeSlug = array_shift($parts);

        $contentType = $this->resolveContentTypeOrFail((string) $contentTypeSlug);

        if (empty($parts)) {
            $e = new ModelNotFoundException();
            $e->setModel(Category::class, []);
            throw $e;
        }

        // Resolve the category path from roots downward within the content type.
        $current = Category::where('content_type_id', $contentType->id)
            ->whereNull('parent_id')
            ->where('slug', array_shift($parts))
            ->first();

        if (! $current) {
            $e = new ModelNotFoundException();
            $e->setModel(Category::class, $slugs);
            throw $e;
        }

        foreach ($parts as $slug) {
            $current = $current->children()->where('slug', $slug)->first();
            if (! $current) {
                $e = new ModelNotFoundException();
                $e->setModel(Category::class, $slugs);
                throw $e;
            }
        }

        $categoryIds = $this->categoryAndDescendantIds($current);

        $query = $this->browseQuery($sort)
            ->whereHas('categories', function ($q) use ($categoryIds) {
                $q->whereIn('categories.id', $categoryIds);
            });

        return $query->cursorPaginate($perPage);
    }

    /**
     * Collect category id plus all descendant category ids.
     *
     * @return array<int, int>
     */
    private function categoryAndDescendantIds(Category $category): array
    {
        $allIds = [(int) $category->id];
        $frontier = [(int) $category->id];

        while (! empty($frontier)) {
            $children = Category::whereIn('parent_id', $frontier)
                ->pluck('id')
                ->map(static fn ($id): int => (int) $id)
                ->all();

            if (empty($children)) {
                break;
            }

            $newIds = array_values(array_diff($children, $allIds));
            if (empty($newIds)) {
                break;
            }

            $allIds = array_values(array_unique(array_merge($allIds, $newIds)));
            $frontier = $newIds;
        }

        return $allIds;
    }

    private function resolveContentTypeOrFail(string $slug): ContentType
    {
        $resolution = $this->contentTypeResolver->resolve($slug);

        if (! $resolution->found() || $resolution->contentType === null) {
            $e = new ModelNotFoundException();
            $e->setModel(ContentType::class, [$slug]);
            throw $e;
        }

        return $resolution->contentType;
    }

    /**
     * Get featured artworks ordered by featured_at DESC, optionally filtered by type.
     * Uses artwork_features table and applies public/approved/published filters.
     */
    private function featuredBaseQuery(?int $type): Builder
    {
        return Artwork::query()
            ->select('artworks.*')
            ->join('artwork_features as af', 'af.artwork_id', '=', 'artworks.id')
            ->leftJoin('artwork_medal_stats as aas', 'aas.artwork_id', '=', 'artworks.id')
            ->where('af.is_active', true)
            ->whereNull('af.deleted_at')
            ->where(function ($query): void {
                $query->whereNull('af.expires_at')
                    ->orWhere('af.expires_at', '>', now());
            })
            ->when($type !== null, function ($q) use ($type) {
                $q->where('af.type', $type);
            });
    }

    private function applyFeaturedEligibilityFilters(Builder $query): void
    {
        $query->public()
            ->published()
            ->tap(fn ($builder) => $this->maturity->applyViewerFilter($builder, request()->user()))
            ->withoutMissingThumbnails();
    }

    private function applyFeaturedOrdering(Builder $query): Builder
    {
        if (Schema::hasColumn('artwork_features', 'force_hero')) {
            $query->orderByDesc('af.force_hero');
        }

        return $query
            ->orderByDesc('af.priority')
            ->orderByRaw('COALESCE(aas.score_30d, 0) DESC')
            ->orderByDesc('af.featured_at')
            ->orderByDesc('artworks.published_at');
    }

    private function featuredSelectionQuery(?int $type): Builder
    {
        $query = $this->featuredBaseQuery($type);
        $this->applyFeaturedEligibilityFilters($query);

        return $this->applyFeaturedOrdering($query);
    }

    private function featuredHeroSelectionQuery(?int $type): Builder
    {
        $query = $this->featuredBaseQuery($type);

        if (Schema::hasColumn('artwork_features', 'force_hero')) {
            $query->where(function (Builder $selection): void {
                $selection->where('af.force_hero', true)
                    ->orWhere(function (Builder $eligible): void {
                        $this->applyFeaturedEligibilityFilters($eligible);
                    });
            });
        } else {
            $this->applyFeaturedEligibilityFilters($query);
        }

        return $this->applyFeaturedOrdering($query);
    }

    public function getFeaturedArtworks(?int $type, int $perPage = 39): LengthAwarePaginator
    {
        return $this->featuredSelectionQuery($type)
            ->with($this->featuredRelations())
            ->paginate($perPage)
            ->withQueryString();
    }

    public function getFeaturedArtworkWinner(?int $type = null): ?Artwork
    {
        $artwork = $this->featuredHeroSelectionQuery($type)
            ->with($this->featuredRelations())
            ->first();

        return $artwork instanceof Artwork ? $artwork : null;
    }

    /**
     * Get artworks belonging to a specific user.
     * If the requester is the owner, return all non-deleted artworks for that user.
     * Public visitors only see public + approved + published artworks.
     *
     * @param int $userId
     * @param bool $isOwner
     * @param int $perPage
     * @return CursorPaginator
     */
    public function getArtworksByUser(int $userId, bool $isOwner, int $perPage = 24, ?User $viewer = null): CursorPaginator
    {
        $query = Artwork::where('user_id', $userId)
            ->with([
                'user:id,name,username,level,rank',
                'user.profile:user_id,avatar_hash',
                'group:id,name,slug,avatar_path',
                'stats:artwork_id,views,downloads,favorites',
                'categories' => function ($q) {
                    $q->select('categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order')
                        ->with(['parent:id,parent_id,content_type_id,name,slug', 'contentType:id,slug,name']);
                },
            ])
            ->orderByDesc('published_at');

        if (! $isOwner) {
            // Apply public visibility constraints for non-owners
            $query->public()->published();
            $this->maturity->applyViewerFilter($query, $viewer);
        } else {
            // Owner: include all non-deleted items (do not force published/approved)
            $query->whereNull('deleted_at');
        }

        return $query->cursorPaginate($perPage);
    }
}
