<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Artwork;
use App\Models\Tag;
use App\Services\ArtworkSearchService;
use App\Services\EarlyGrowth\EarlyGrowth;
use App\Services\EarlyGrowth\GridFiller;
use App\Services\Recommendation\RecommendationService;
use App\Services\UserPreferenceService;
use App\Support\AvatarUrl;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Database\QueryException;

/**
 * HomepageService
 *
 * Aggregates all data sections needed for the Nova homepage.
 * All results are cached for CACHE_TTL seconds.
 * Controllers stay thin — only call the aggregator.
 */
final class HomepageService
{
    private const CACHE_TTL = 300; // 5 minutes

    public function __construct(
        private readonly ArtworkService          $artworks,
        private readonly ArtworkSearchService    $search,
        private readonly UserPreferenceService   $prefs,
        private readonly RecommendationService   $reco,
        private readonly GridFiller              $gridFiller,
    ) {}

    // ─────────────────────────────────────────────────────────────────────────
    // Public aggregator
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Return all homepage section data as a single array ready to JSON-encode.
     */
    public function all(): array
    {
        return [
            'hero'     => $this->getHeroArtwork(),
            'rising'   => $this->getRising(),
            'trending' => $this->getTrending(),
            'fresh'    => $this->getFreshUploads(),
            'tags'     => $this->getPopularTags(),
            'creators' => $this->getCreatorSpotlight(),
            'news'     => $this->getNews(),
        ];
    }

    /**
     * Personalized homepage data for an authenticated user.
     *
     * Sections:
     *   1. user_data        – welcome row counts (messages, notifications, new followers)
     *   2. from_following   – artworks from creators you follow
     *   3. trending         – same trending feed as guests
     *   4. by_tags          – artworks matching user's top tags (Trending For You)
     *   5. by_categories    – fresh uploads in user's favourite categories
     *   6. suggested_creators – creators the user might want to follow
     *   7. tags / creators / news – shared with guest homepage
     */
    public function allForUser(\App\Models\User $user): array
    {
        $prefs = $this->prefs->build($user);

        return [
            'is_logged_in'        => true,
            'user_data'           => $this->getUserData($user),
            'hero'                => $this->getHeroArtwork(),
            'for_you'             => $this->getForYouPreview($user),
            'from_following'      => $this->getFollowingFeed($user, $prefs),
            'rising'              => $this->getRising(),
            'trending'            => $this->getTrending(),
            'fresh'               => $this->getFreshUploads(),
            'by_tags'             => $this->getByTags($prefs['top_tags'] ?? []),
            'by_categories'       => $this->getByCategories($prefs['top_categories'] ?? []),
            'suggested_creators'  => $this->getSuggestedCreators($user, $prefs),
            'tags'                => $this->getPopularTags(),
            'creators'            => $this->getCreatorSpotlight(),
            'news'                => $this->getNews(),
            'preferences'         => [
                'top_tags'       => $prefs['top_tags']       ?? [],
                'top_categories' => $prefs['top_categories'] ?? [],
            ],
        ];
    }

    /**
     * "For You" homepage preview: first 12 results from the Phase 1 personalised feed.
     *
     * Uses RecommendationService which handles Meilisearch retrieval, PHP reranking,
     * diversity controls, and its own Redis cache layer.
     */
    public function getForYouPreview(\App\Models\User $user, int $limit = 12): array
    {
        try {
            return $this->reco->forYouPreview($user, $limit);
        } catch (\Throwable $e) {
            Log::warning('HomepageService::getForYouPreview failed', ['error' => $e->getMessage()]);
            return [];
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Sections
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Hero artwork: first item from the featured list.
     */
    public function getHeroArtwork(): ?array
    {
        return Cache::remember('homepage.hero', self::CACHE_TTL, function (): ?array {
            $result = $this->artworks->getFeaturedArtworks(null, 1);

            /** @var \Illuminate\Database\Eloquent\Model|\null $artwork */
            if ($result instanceof \Illuminate\Pagination\LengthAwarePaginator) {
                $artwork = $result->getCollection()->first();
            } elseif ($result instanceof \Illuminate\Support\Collection) {
                $artwork = $result->first();
            } elseif (is_array($result)) {
                $artwork = $result[0] ?? null;
            } else {
                $artwork = null;
            }

            return $artwork ? $this->serializeArtwork($artwork, 'lg') : null;
        });
    }

    /**
     * Rising Now: up to 10 artworks sorted by heat_score (updated every 15 min).
     *
     * Surfaces artworks with the fastest recent engagement growth.
     * Falls back to DB ORDER BY heat_score if Meilisearch is unavailable.
     */
    public function getRising(int $limit = 10): array
    {
        $cutoff = now()->subDays(30)->toDateString();

        return Cache::remember("homepage.rising.{$limit}", 120, function () use ($limit, $cutoff): array {
            try {
                $results = Artwork::search('')
                    ->options([
                        'filter' => 'is_public = true AND is_approved = true AND created_at >= "' . $cutoff . '"',
                        'sort'   => ['heat_score:desc', 'engagement_velocity:desc', 'created_at:desc'],
                    ])
                    ->paginate($limit, 'page', 1);

                $results->getCollection()->load(['user:id,name,username', 'user.profile:user_id,avatar_hash']);

                if ($results->isEmpty()) {
                    return $this->getRisingFromDb($limit);
                }

                return $results->getCollection()
                    ->map(fn ($a) => $this->serializeArtwork($a))
                    ->values()
                    ->all();
            } catch (\Throwable $e) {
                Log::warning('HomepageService::getRising Meilisearch unavailable, DB fallback', [
                    'error' => $e->getMessage(),
                ]);

                return $this->getRisingFromDb($limit);
            }
        });
    }

    /**
     * DB-only fallback for rising (Meilisearch unavailable).
     */
    private function getRisingFromDb(int $limit): array
    {
        return Artwork::public()
            ->published()
            ->with(['user:id,name,username', 'user.profile:user_id,avatar_hash'])
            ->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id')
            ->select('artworks.*')
            ->where('artworks.published_at', '>=', now()->subDays(30))
            ->orderByDesc('artwork_stats.heat_score')
            ->orderByDesc('artwork_stats.engagement_velocity')
            ->limit($limit)
            ->get()
            ->map(fn ($a) => $this->serializeArtwork($a))
            ->values()
            ->all();
    }

    /**
     * Trending: up to 12 artworks sorted by Ranking V2 `ranking_score`.
     *
     * Uses Meilisearch sorted by the V2 score (updated every 30 min).
     * Falls back to DB ORDER BY ranking_score if Meilisearch is unavailable.
     * Spec §6: ranking_score, last 30 days, highlight high-velocity artworks.
     */
    public function getTrending(int $limit = 10): array
    {
        $cutoff = now()->subDays(30)->toDateString();

        return Cache::remember("homepage.trending.{$limit}", self::CACHE_TTL, function () use ($limit, $cutoff): array {
            try {
                $results = Artwork::search('')
                    ->options([
                        'filter' => 'is_public = true AND is_approved = true AND created_at >= "' . $cutoff . '"',
                        'sort'   => ['ranking_score:desc', 'engagement_velocity:desc', 'views:desc'],
                    ])
                    ->paginate($limit, 'page', 1);

                $results->getCollection()->load(['user:id,name,username', 'user.profile:user_id,avatar_hash']);

                if ($results->isEmpty()) {
                    return $this->getTrendingFromDb($limit);
                }

                return $results->getCollection()
                    ->map(fn ($a) => $this->serializeArtwork($a))
                    ->values()
                    ->all();
            } catch (\Throwable $e) {
                Log::warning('HomepageService::getTrending Meilisearch unavailable, DB fallback', [
                    'error' => $e->getMessage(),
                ]);

                return $this->getTrendingFromDb($limit);
            }
        });
    }

    /**
     * DB-only fallback for trending (Meilisearch unavailable).
     * Joins artwork_stats to sort by V2 ranking_score.
     */
    private function getTrendingFromDb(int $limit): array
    {
        return Artwork::public()
            ->published()
            ->with(['user:id,name,username', 'user.profile:user_id,avatar_hash'])
            ->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id')
            ->select('artworks.*')
            ->where('artworks.published_at', '>=', now()->subDays(30))
            ->orderByDesc('artwork_stats.ranking_score')
            ->orderByDesc('artwork_stats.engagement_velocity')
            ->limit($limit)
            ->get()
            ->map(fn ($a) => $this->serializeArtwork($a))
            ->values()
            ->all();
    }

    /**
     * Fresh uploads: latest 10 approved public artworks.
     * EGS: GridFiller ensures the section is never empty even on low-traffic days.
     */
    public function getFreshUploads(int $limit = 10): array
    {
        // Include EGS mode in cache key so toggling EGS updates the section within TTL
        $egsKey   = EarlyGrowth::gridFillerEnabled() ? 'egs-' . EarlyGrowth::mode() : 'std';
        $cacheKey = "homepage.fresh.{$limit}.{$egsKey}";

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($limit): array {
            $artworks = Artwork::public()
                ->published()
                ->with(['user:id,name,username', 'user.profile:user_id,avatar_hash'])
                ->orderByDesc('published_at')
                ->limit($limit)
                ->get();

            // EGS: fill up to $limit when fresh uploads are sparse
            $artworks = $this->gridFiller->fillCollection($artworks, $limit);

            return $artworks->map(fn ($a) => $this->serializeArtwork($a))->values()->all();
        });
    }

    /**
     * Top 12 popular tags by usage_count.
     */
    public function getPopularTags(int $limit = 12): array
    {
        return Cache::remember("homepage.tags.{$limit}", self::CACHE_TTL, function () use ($limit): array {
            return Tag::query()
                ->where('is_active', true)
                ->orderByDesc('usage_count')
                ->limit($limit)
                ->get(['id', 'name', 'slug', 'usage_count'])
                ->map(fn ($t) => [
                    'id'    => $t->id,
                    'name'  => $t->name,
                    'slug'  => $t->slug,
                    'count' => (int) $t->usage_count,
                ])
                ->values()
                ->all();
        });
    }

    /**
     * Creator spotlight: top 6 creators by weekly uploads, awards, and engagement.
     * "Weekly uploads" drives ranking per spec; ties broken by total awards then views.
     */
    public function getCreatorSpotlight(int $limit = 6): array
    {
        return Cache::remember("homepage.creators.{$limit}", self::CACHE_TTL, function () use ($limit): array {
            try {
                $since = now()->subWeek();

                $rows = DB::table('artworks')
                    ->join('users as u', 'u.id', '=', 'artworks.user_id')
                    ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
                    ->leftJoin('artwork_awards as aw', 'aw.artwork_id', '=', 'artworks.id')
                    ->leftJoin('artwork_stats as s', 's.artwork_id', '=', 'artworks.id')
                    ->select(
                        'u.id',
                        'u.name',
                        'u.username',
                        'up.avatar_hash',
                        DB::raw('COUNT(DISTINCT artworks.id) as upload_count'),
                        DB::raw('SUM(CASE WHEN artworks.published_at >= \'' . $since->toDateTimeString() . '\' THEN 1 ELSE 0 END) as weekly_uploads'),
                        DB::raw('COALESCE(SUM(s.views), 0) as total_views'),
                        DB::raw('COUNT(DISTINCT aw.id) as total_awards')
                    )
                    ->where('artworks.is_public', true)
                    ->where('artworks.is_approved', true)
                    ->whereNull('artworks.deleted_at')
                    ->whereNotNull('artworks.published_at')
                    ->groupBy('u.id', 'u.name', 'u.username', 'up.avatar_hash')
                    ->orderByDesc('weekly_uploads')
                    ->orderByDesc('total_awards')
                    ->orderByDesc('total_views')
                    ->limit($limit)
                    ->get();

                $userIds = $rows->pluck('id')->all();

                // Pick one random artwork thumbnail per creator for the card background.
                $thumbsByUser = Artwork::public()
                    ->published()
                    ->whereIn('user_id', $userIds)
                    ->whereNotNull('hash')
                    ->whereNotNull('thumb_ext')
                    ->inRandomOrder()
                    ->get(['id', 'user_id', 'hash', 'thumb_ext'])
                    ->groupBy('user_id');

                return $rows->map(function ($u) use ($thumbsByUser) {
                    $artworkForBg = $thumbsByUser->get($u->id)?->first();
                    $bgThumb      = $artworkForBg ? $artworkForBg->thumbUrl('md') : null;

                    return [
                        'id'             => $u->id,
                        'name'           => $u->name,
                        'uploads'        => (int) $u->upload_count,
                        'weekly_uploads' => (int) $u->weekly_uploads,
                        'views'          => (int) $u->total_views,
                        'awards'         => (int) $u->total_awards,
                        'url'            => $u->username ? '/@' . $u->username : '/profile/' . $u->id,
                        'avatar'         => AvatarUrl::forUser((int) $u->id, $u->avatar_hash ?: null, 128),
                        'bg_thumb'       => $bgThumb,
                    ];
                })->values()->all();
            } catch (QueryException $e) {
                Log::warning('HomepageService::getCreatorSpotlight DB error', [
                    'exception' => $e->getMessage(),
                ]);

                return [];
            }
        });
    }

    /**
     * Latest 5 news posts from the forum news category.
     */
    public function getNews(int $limit = 5): array
    {
        return Cache::remember("homepage.news.{$limit}", self::CACHE_TTL, function () use ($limit): array {
            try {
                $items = DB::table('forum_threads as t')
                    ->leftJoin('forum_categories as c', 'c.id', '=', 't.category_id')
                    ->select('t.id', 't.title', 't.created_at', 't.slug as thread_slug')
                    ->where(function ($q) {
                        $q->where('t.category_id', 2876)
                          ->orWhereIn('c.slug', ['news', 'forum-news']);
                    })
                    ->whereNull('t.deleted_at')
                    ->orderByDesc('t.created_at')
                    ->limit($limit)
                    ->get();

                return $items->map(fn ($row) => [
                    'id'    => $row->id,
                    'title' => $row->title,
                    'date'  => $row->created_at,
                    'url'   => '/forum/thread/' . $row->id . '-' . ($row->thread_slug ?? 'post'),
                ])->values()->all();
            } catch (QueryException $e) {
                Log::warning('HomepageService::getNews DB error', [
                    'exception' => $e->getMessage(),
                ]);

                return [];
            }
        });
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Personalized sections (auth only)
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Welcome-row counts: unread messages, unread notifications, new followers.
     * Returns quickly from DB using simple COUNTs; never throws.
     */
    public function getUserData(\App\Models\User $user): array
    {
        try {
            $unreadMessages = DB::table('conversations as c')
                ->join('conversation_participants as cp', 'cp.conversation_id', '=', 'c.id')
                ->join('messages as m', 'm.conversation_id', '=', 'c.id')
                ->where('cp.user_id', $user->id)
                ->where('m.user_id', '!=', $user->id)
                ->whereColumn('m.created_at', '>', 'cp.last_read_at')
                ->distinct('c.id')
                ->count('c.id');
        } catch (\Throwable) {
            $unreadMessages = 0;
        }

        try {
            $unreadNotifications = DB::table('notifications')
                ->where('user_id', $user->id)
                ->whereNull('read_at')
                ->count();
        } catch (\Throwable) {
            $unreadNotifications = 0;
        }

        return [
            'id'                   => $user->id,
            'name'                 => $user->name,
            'username'             => $user->username,
            'avatar'               => AvatarUrl::forUser((int) $user->id, $user->profile?->avatar_hash ?? null, 64),
            'messages_unread'      => (int) $unreadMessages,
            'notifications_unread' => (int) $unreadNotifications,
            'followers_count'      => (int) ($user->statistics?->followers_count ?? 0),
        ];
    }

    /**
     * Suggested creators: active public uploaders NOT already followed by the user,
     * ranked by follower count. Optionally filtered to the user's top categories.
     */
    public function getSuggestedCreators(\App\Models\User $user, array $prefs, int $limit = 8): array
    {
        return Cache::remember(
            "homepage.suggested.{$user->id}",
            300,
            function () use ($user, $prefs, $limit): array {
                try {
                    $followingIds = $prefs['followed_creators'] ?? [];

                    $query = DB::table('users as u')
                        ->join('user_profiles as up', 'up.user_id', '=', 'u.id')
                        ->leftJoin('user_statistics as us', 'us.user_id', '=', 'u.id')
                        ->select(
                            'u.id',
                            'u.name',
                            'u.username',
                            'up.avatar_hash',
                            DB::raw('COALESCE(us.followers_count, 0) as followers_count'),
                            DB::raw('COALESCE(us.artworks_count, 0) as artworks_count'),
                        )
                        ->where('u.id', '!=', $user->id)
                        ->whereNotIn('u.id', array_merge($followingIds, [$user->id]))
                        ->where('u.is_active', true)
                        ->orderByDesc('followers_count')
                        ->orderByDesc('artworks_count')
                        ->limit($limit);

                    $rows = $query->get();

                    return $rows->map(fn ($u) => [
                        'id'              => $u->id,
                        'name'            => $u->name,
                        'username'        => $u->username,
                        'url'             => $u->username ? '/@' . $u->username : '/profile/' . $u->id,
                        'avatar'          => AvatarUrl::forUser((int) $u->id, $u->avatar_hash ?: null, 64),
                        'followers_count' => (int) $u->followers_count,
                        'artworks_count'  => (int) $u->artworks_count,
                    ])->values()->all();
                } catch (\Throwable $e) {
                    Log::warning('HomepageService::getSuggestedCreators failed', ['error' => $e->getMessage()]);
                    return [];
                }
            }
        );
    }

    /**
     * Latest artworks from creators the user follows (max 12).
     */
    public function getFollowingFeed(\App\Models\User $user, array $prefs): array
    {
        $followingIds = $prefs['followed_creators'] ?? [];

        if (empty($followingIds)) {
            return [];
        }

        return Cache::remember(
            "homepage.following.{$user->id}",
            60, // short TTL – personal data
            function () use ($followingIds): array {
                $artworks = Artwork::public()
                    ->published()
                    ->with(['user:id,name,username', 'user.profile:user_id,avatar_hash'])
                    ->whereIn('user_id', $followingIds)
                    ->orderByDesc('published_at')
                    ->limit(10)
                    ->get();

                return $artworks->map(fn ($a) => $this->serializeArtwork($a))->values()->all();
            }
        );
    }

    /**
     * Artworks matching the user's top tags (max 12).
     * Powered by Meilisearch.
     */
    public function getByTags(array $tagSlugs): array
    {
        if (empty($tagSlugs)) {
            return [];
        }

        try {
            $results = $this->search->discoverByTags($tagSlugs, 12);

            return $results->getCollection()
                ->map(fn ($a) => $this->serializeArtwork($a))
                ->values()
                ->all();
        } catch (\Throwable $e) {
            Log::warning('HomepageService::getByTags failed', ['error' => $e->getMessage()]);
            return [];
        }
    }

    /**
     * Fresh artworks in the user's favourite categories (max 12).
     * Powered by Meilisearch.
     */
    public function getByCategories(array $categorySlugs): array
    {
        if (empty($categorySlugs)) {
            return [];
        }

        try {
            $results = $this->search->discoverByCategories($categorySlugs, 12);

            return $results->getCollection()
                ->map(fn ($a) => $this->serializeArtwork($a))
                ->values()
                ->all();
        } catch (\Throwable $e) {
            Log::warning('HomepageService::getByCategories failed', ['error' => $e->getMessage()]);
            return [];
        }
    }

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

    private function serializeArtwork(Artwork $artwork, string $preferSize = 'md'): array
    {
        $thumbMd = $artwork->thumbUrl('md');
        $thumbLg = $artwork->thumbUrl('lg');
        $thumb   = $preferSize === 'lg' ? ($thumbLg ?? $thumbMd) : ($thumbMd ?? $thumbLg);
        $primaryCategory = $artwork->categories->sortBy('sort_order')->first();

        $authorId       = $artwork->user_id;
        $authorName     = $artwork->user?->name ?? 'Artist';
        $authorUsername = $artwork->user?->username ?? '';
        $avatarHash     = $artwork->user?->profile?->avatar_hash ?? null;
        $authorAvatar   = AvatarUrl::forUser((int) $authorId, $avatarHash, 64);

        return [
            'id'              => $artwork->id,
            'title'           => $artwork->title ?? 'Untitled',
            'slug'            => $artwork->slug,
            'author'          => $authorName,
            'author_id'       => $authorId,
            'author_username' => $authorUsername,
            'author_avatar'   => $authorAvatar,
            'thumb'           => $thumb,
            'thumb_md'        => $thumbMd,
            'thumb_lg'        => $thumbLg,
            'category_name'   => $primaryCategory->name ?? '',
            'category_slug'   => $primaryCategory->slug ?? '',
            'content_type_name' => $primaryCategory?->contentType?->name ?? '',
            'content_type_slug' => $primaryCategory?->contentType?->slug ?? '',
            'url'             => '/art/' . $artwork->id . '/' . ($artwork->slug ?? ''),
            'width'           => $artwork->width,
            'height'          => $artwork->height,
            'published_at'    => $artwork->published_at?->toIso8601String(),
        ];
    }
}
