<?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 = (favorites_velocity * wf)
 *         + (comments_velocity * wc)
 *         + (shares_velocity * ws)
 *         + (downloads_velocity * wd)
 *         + (views_velocity * wv)
 *         - (hours_since_published * 0.1)
 *
 * The score is stored in artworks.trending_score_1h / 24h / 7d.
 *
 * 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    '1h' targets trending_score_1h  (3-day window)
    *                          '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) {
            '1h' => ['trending_score_1h', 3],
            '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) {
            '1h' => ['views_1h', 'downloads_1h'],
            '24h'  => ['views_24h', 'downloads_24h'],
            default => ['views_7d',  'downloads_7d'],
        };

        [$favCol, $commentCol, $shareCol] = match ($period) {
            '1h' => ['favourites_1h', 'comments_1h', 'shares_1h'],
            '24h' => ['favourites_24h', 'comments_24h', 'shares_24h'],
            default => ['favorites', 'comments_count', 'shares_count'],
        };

        $weights = (array) config('discovery.v2.trending.velocity_weights', []);
        $wView = (float) ($weights['views'] ?? self::W_VIEW);
        $wFavorite = (float) ($weights['favorites'] ?? self::W_FAVORITE);
        $wComment = (float) ($weights['comments'] ?? self::W_REACTION);
        $wShare = (float) ($weights['shares'] ?? self::W_DOWNLOAD);

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

        Artwork::query()
            ->select('id')
            ->catalogVisible()
            ->where('published_at', '>=', $cutoff)
            ->orderBy('id')
            ->chunkById($chunkSize, function ($artworks) use ($column, $viewCol, $dlCol, $favCol, $commentCol, $shareCol, $wFavorite, $wComment, $wShare, $wView, &$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 {$favCol}     FROM artwork_stats        WHERE artwork_stats.artwork_id        = artworks.id), 0) * ?
                           + COALESCE((SELECT {$commentCol} FROM artwork_stats        WHERE artwork_stats.artwork_id        = artworks.id), 0) * ?
                           + COALESCE((SELECT {$shareCol}   FROM artwork_stats        WHERE artwork_stats.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(
                        [$wFavorite, $wComment, $wShare, self::W_DOWNLOAD, $wView, 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 = match ($period) {
            '1h' => 3,
            '24h' => 7,
            default => 30,
        };
        $cutoff     = now()->subDays($windowDays)->toDateTimeString();

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