<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Artwork;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * TrendingService
 *
 * Calculates and persists deterministic trending scores for artworks.
 *
 * Formula (Phase 1):
 *   score = (award_score * 5)
 *         + (favorites_count * 3)
 *         + (reactions_count * 2)
 *         + (downloads_count * 1)
 *         + (views * 2)
 *         - (hours_since_published * 0.1)
 *
 * The score is stored in artworks.trending_score_24h (artworks ≤ 7 days old)
 * and artworks.trending_score_7d (artworks ≤ 30 days old).
 *
 * Both columns are updated every run; use `--period` to limit computation.
 */
final class TrendingService
{
    /** Weight constants — tune via config('discovery.trending.*') if needed */
    private const W_AWARD     = 5.0;
    private const W_FAVORITE  = 3.0;
    private const W_REACTION  = 2.0;
    private const W_DOWNLOAD  = 1.0;
    private const W_VIEW      = 2.0;
    private const DECAY_RATE  = 0.1; // score loss per hour since publish

    /**
     * Recalculate trending scores for artworks published within the look-back window.
     *
     * @param string $period    '24h' targets trending_score_24h (7-day window)
     *                          '7d'  targets trending_score_7d  (30-day window)
     * @param int    $chunkSize Number of IDs per DB UPDATE batch
     * @return int   Number of artworks updated
     */
    public function recalculate(string $period = '7d', int $chunkSize = 1000): int
    {
        [$column, $windowDays] = match ($period) {
            '24h'  => ['trending_score_24h', 7],
            default => ['trending_score_7d', 30],
        };

        // Use the windowed counters: views_24h/views_7d and downloads_24h/downloads_7d
        // instead of all-time totals so trending reflects recent activity.
        [$viewCol, $dlCol] = match ($period) {
            '24h'  => ['views_24h', 'downloads_24h'],
            default => ['views_7d',  'downloads_7d'],
        };

        $cutoff  = now()->subDays($windowDays)->toDateTimeString();
        $updated = 0;

        Artwork::query()
            ->select('id')
            ->where('is_public', true)
            ->where('is_approved', true)
            ->whereNull('deleted_at')
            ->whereNotNull('published_at')
            ->where('published_at', '>=', $cutoff)
            ->orderBy('id')
            ->chunkById($chunkSize, function ($artworks) use ($column, $viewCol, $dlCol, &$updated): void {
                $ids        = $artworks->pluck('id')->toArray();
                $inClause   = implode(',', array_fill(0, count($ids), '?'));

                // One bulk UPDATE per chunk – uses pre-computed windowed counters
                // for views and downloads (accurate rolling windows, reset nightly/weekly)
                // rather than all-time totals. All other signals use correlated subqueries.
                // Column name ($column) is controlled internally, not user-supplied.
                DB::update(
                    "UPDATE artworks
                     SET
                         {$column} = GREATEST(
                             COALESCE((SELECT score_total  FROM artwork_award_stats  WHERE artwork_award_stats.artwork_id  = artworks.id), 0) * ?
                           + COALESCE((SELECT favorites    FROM artwork_stats        WHERE artwork_stats.artwork_id        = artworks.id), 0) * ?
                           + COALESCE((SELECT COUNT(*)     FROM artwork_reactions    WHERE artwork_reactions.artwork_id    = artworks.id), 0) * ?
                           + COALESCE((SELECT {$dlCol}     FROM artwork_stats        WHERE artwork_stats.artwork_id        = artworks.id), 0) * ?
                           + COALESCE((SELECT {$viewCol}   FROM artwork_stats        WHERE artwork_stats.artwork_id        = artworks.id), 0) * ?
                           - (TIMESTAMPDIFF(HOUR, artworks.published_at, NOW()) * ?)
                         , 0),
                         last_trending_calculated_at = NOW()
                     WHERE id IN ({$inClause})",
                    array_merge(
                        [self::W_AWARD, self::W_FAVORITE, self::W_REACTION, self::W_DOWNLOAD, self::W_VIEW, self::DECAY_RATE],
                        $ids
                    )
                );

                $updated += count($ids);
            });

        Log::info('TrendingService: recalculation complete', [
            'period'  => $period,
            'column'  => $column,
            'updated' => $updated,
        ]);

        return $updated;
    }

    /**
     * Dispatch Meilisearch re-index jobs for artworks in the trending window.
     * Called after recalculate() to keep the search index current.
     */
    public function syncToSearchIndex(string $period = '7d', int $chunkSize = 500): void
    {
        $windowDays = $period === '24h' ? 7 : 30;
        $cutoff     = now()->subDays($windowDays)->toDateTimeString();

        Artwork::query()
            ->select('id')
            ->where('is_public', true)
            ->where('is_approved', true)
            ->whereNull('deleted_at')
            ->where('published_at', '>=', $cutoff)
            ->chunkById($chunkSize, function ($artworks): void {
                foreach ($artworks as $artwork) {
                    \App\Jobs\IndexArtworkJob::dispatch($artwork->id);
                }
            });
    }
}
