<?php
namespace App\Services;

use App\Models\Artwork;
use App\Models\Category;
use App\Models\ContentType;
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;

/**
 * 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

    /**
     * Shared browse query used by /browse, content-type pages, and category pages.
     */
    private function browseQuery(string $sort = 'latest'): Builder
    {
        $query = Artwork::public()
            ->published()
            ->with([
                'user:id,name',
                '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']);
                },
            ]);

        $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()
            ->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()
            ->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 = ContentType::where('slug', strtolower($slug))->first();

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

        $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 = ContentType::where('slug', $contentTypeSlug)->first();
        if (! $contentType) {
            $e = new ModelNotFoundException();
            $e->setModel(ContentType::class, [$contentTypeSlug]);
            throw $e;
        }

        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;
    }

    /**
     * Get featured artworks ordered by featured_at DESC, optionally filtered by type.
     * Uses artwork_features table and applies public/approved/published filters.
     */
    public function getFeaturedArtworks(?int $type, int $perPage = 39): LengthAwarePaginator
    {
        $query = Artwork::query()
            ->select('artworks.*')
            ->join('artwork_features as af', 'af.artwork_id', '=', 'artworks.id')
            ->public()
            ->published()
            ->when($type !== null, function ($q) use ($type) {
                $q->where('af.type', $type);
            })
            ->with([
                'user:id,name,username',
                'categories' => function ($q) {
                    $q->select('categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order');
                },
            ])
            ->orderByDesc('af.featured_at')
            ->orderByDesc('artworks.published_at');

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

    /**
     * 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): CursorPaginator
    {
        $query = Artwork::where('user_id', $userId)
            ->with([
                'user:id,name,username,level,rank',
                '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();
        } else {
            // Owner: include all non-deleted items (do not force published/approved)
            $query->whereNull('deleted_at');
        }

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