<?php

namespace App\Services\Posts;

use App\Models\Post;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

/**
 * Computes the trending post feed with Wilson/decay scoring and author diversity.
 *
 * Score formula (per spec):
 *   base  = (likes * 3) + (comments * 5) + (shares * 6) + (unique_reactors * 4)
 *   score = base * exp(-hours_since_post / 24)
 *
 * Diversity rule: max 2 posts per author in the top N results.
 * Cache TTL: 2 minutes.
 */
class PostTrendingService
{
    private const CACHE_KEY  = 'feed:trending';
    private const CACHE_TTL  = 120; // seconds
    private const WINDOW_DAYS = 7;
    private const MAX_PER_AUTHOR = 2;

    private PostFeedService $feedService;

    public function __construct(PostFeedService $feedService)
    {
        $this->feedService = $feedService;
    }

    /**
     * Return trending posts for the given viewer.
     *
     * @param int|null $viewerId
     * @param int      $page
     * @param int      $perPage
     * @return array{data: array, meta: array}
     */
    public function getTrending(?int $viewerId, int $page = 1, int $perPage = 20): array
    {
        $rankedIds = $this->getRankedIds();

        // Paginate from the ranked ID list
        $total   = count($rankedIds);
        $pageIds = array_slice($rankedIds, ($page - 1) * $perPage, $perPage);

        if (empty($pageIds)) {
            return ['data' => [], 'meta' => ['total' => $total, 'current_page' => $page, 'last_page' => (int) ceil($total / $perPage) ?: 1, 'per_page' => $perPage]];
        }

        // Load posts preserving ranked order
        $posts = Post::with($this->feedService->publicEagerLoads())
            ->withExists([
                'saves as viewer_saved' => fn ($saveQuery) => $saveQuery->where('user_id', $viewerId),
            ])
            ->whereIn('id', $pageIds)
            ->get()
            ->keyBy('id');

        $ordered = array_filter(array_map(fn ($id) => $posts->get($id), $pageIds));
        $data    = array_values(array_map(
            fn ($post) => $this->feedService->formatPost($post, $viewerId),
            $ordered,
        ));

        return [
            'data' => $data,
            'meta' => [
                'total'        => $total,
                'current_page' => $page,
                'last_page'    => (int) ceil($total / $perPage) ?: 1,
                'per_page'     => $perPage,
            ],
        ];
    }

    /**
     * Get or compute the ranked post-ID list from cache.
     *
     * @return int[]
     */
    public function getRankedIds(): array
    {
        return Cache::remember(self::CACHE_KEY, self::CACHE_TTL, function () {
            return $this->computeRankedIds();
        });
    }

    /** Force a cache refresh (called by the CLI command). */
    public function refresh(): array
    {
        Cache::forget(self::CACHE_KEY);
        return $this->getRankedIds();
    }

    // ─────────────────────────────────────────────────────────────────────────

    private function computeRankedIds(): array
    {
        $cutoff = now()->subDays(self::WINDOW_DAYS);

        $rows = DB::table('posts')
            ->leftJoin(
                DB::raw('(SELECT post_id, COUNT(*) as unique_reactors FROM post_reactions GROUP BY post_id) pr'),
                'posts.id', '=', 'pr.post_id',
            )
            ->where('posts.status', Post::STATUS_PUBLISHED)
            ->where('posts.visibility', Post::VISIBILITY_PUBLIC)
            ->where('posts.created_at', '>=', $cutoff)
            ->whereNull('posts.deleted_at')
            ->select([
                'posts.id',
                'posts.user_id',
                'posts.reactions_count',
                'posts.comments_count',
                'posts.created_at',
                DB::raw('COALESCE(pr.unique_reactors, 0) as unique_reactors'),
            ])
            ->get();

        $now = now()->timestamp;

        $scored = $rows->map(function ($row) use ($now) {
            $hoursSince = ($now - strtotime($row->created_at)) / 3600;
            $base = ($row->reactions_count * 3)
                  + ($row->comments_count  * 5)
                  + ($row->unique_reactors * 4);
            $score = $base * exp(-$hoursSince / 24);

            return ['id' => $row->id, 'user_id' => $row->user_id, 'score' => $score];
        })->sortByDesc('score');

        // Apply author diversity: max MAX_PER_AUTHOR posts per author
        $authorCount = [];
        $result      = [];

        foreach ($scored as $item) {
            $uid = $item['user_id'];
            $authorCount[$uid] = ($authorCount[$uid] ?? 0) + 1;
            if ($authorCount[$uid] <= self::MAX_PER_AUTHOR) {
                $result[] = $item['id'];
            }
        }

        return $result;
    }
}
