<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Services\Recommendation\UserPreferenceBuilder;
use App\Support\AvatarUrl;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * GET /api/user/suggestions/creators
 *
 * Returns up to 12 creators the authenticated user might want to follow.
 *
 * Ranking algorithm (Phase 1 – no embeddings):
 *   1. Creators followed by people you follow (mutual-follow signal)
 *   2. Creators whose recent works overlap your top tags
 *   3. High-quality creators (followers_count / artworks_count) in your categories
 *
 * Exclusions: yourself, already-followed creators.
 *
 * Cached per user for config('recommendations.ttl.creator_suggestions') seconds (default 30 min).
 */
final class SuggestedCreatorsController extends Controller
{
    private const LIMIT = 12;

    public function __construct(private readonly UserPreferenceBuilder $prefBuilder) {}

    public function __invoke(Request $request): JsonResponse
    {
        $user = $request->user();

        $ttl      = (int) config('recommendations.ttl.creator_suggestions', 30 * 60);
        $cacheKey = "creator_suggestions:{$user->id}";

        $data = Cache::remember($cacheKey, $ttl, function () use ($user) {
            return $this->buildSuggestions($user);
        });

        return response()->json(['data' => $data]);
    }

    private function buildSuggestions(\App\Models\User $user): array
    {
        try {
            $profile     = $this->prefBuilder->build($user);
            $followingIds = $profile->strongCreatorIds;
            $topTagSlugs  = array_slice($profile->topTagSlugs, 0, 10);

            // ── 1. Mutual-follow candidates ───────────────────────────────────
            $mutualCandidates = [];
            if ($followingIds !== []) {
                $rows = DB::table('user_followers as uf')
                    ->join('users as u', 'u.id', '=', 'uf.user_id')
                    ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
                    ->leftJoin('user_statistics as us', 'us.user_id', '=', 'u.id')
                    ->whereIn('uf.follower_id', $followingIds)
                    ->where('uf.user_id', '!=', $user->id)
                    ->whereNotIn('uf.user_id', array_merge($followingIds, [$user->id]))
                    ->where('u.is_active', true)
                    ->selectRaw('
                        u.id,
                        u.name,
                        u.username,
                        up.avatar_hash,
                        COALESCE(us.followers_count, 0) as followers_count,
                        COALESCE(us.artworks_count, 0)  as artworks_count,
                        COUNT(*) as mutual_weight
                    ')
                    ->groupBy('u.id', 'u.name', 'u.username', 'up.avatar_hash', 'us.followers_count', 'us.artworks_count')
                    ->orderByDesc('mutual_weight')
                    ->limit(20)
                    ->get();

                foreach ($rows as $row) {
                    $mutualCandidates[(int) $row->id] = [
                        'id'              => (int) $row->id,
                        'name'            => $row->name,
                        'username'        => $row->username,
                        'avatar_hash'     => $row->avatar_hash,
                        'followers_count' => (int) $row->followers_count,
                        'artworks_count'  => (int) $row->artworks_count,
                        'score'           => (float) $row->mutual_weight * 3.0,
                        'reason'          => 'Popular among creators you follow',
                    ];
                }
            }

            // ── 2. Tag-affinity candidates ────────────────────────────────────
            $tagCandidates = [];
            if ($topTagSlugs !== []) {
                $tagFilter = implode(',', array_fill(0, count($topTagSlugs), '?'));

                $rows = DB::table('tags as t')
                    ->join('artwork_tag as at', 'at.tag_id', '=', 't.id')
                    ->join('artworks as a', 'a.id', '=', 'at.artwork_id')
                    ->join('users as u', 'u.id', '=', 'a.user_id')
                    ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
                    ->leftJoin('user_statistics as us', 'us.user_id', '=', 'u.id')
                    ->whereIn('t.slug', $topTagSlugs)
                    ->where('a.is_public', true)
                    ->where('a.is_approved', true)
                    ->whereNull('a.deleted_at')
                    ->where('u.id', '!=', $user->id)
                    ->whereNotIn('u.id', array_merge($followingIds, [$user->id]))
                    ->where('u.is_active', true)
                    ->selectRaw('
                        u.id,
                        u.name,
                        u.username,
                        up.avatar_hash,
                        COALESCE(us.followers_count, 0) as followers_count,
                        COALESCE(us.artworks_count, 0)  as artworks_count,
                        COUNT(DISTINCT t.id) as matched_tags
                    ')
                    ->groupBy('u.id', 'u.name', 'u.username', 'up.avatar_hash', 'us.followers_count', 'us.artworks_count')
                    ->orderByDesc('matched_tags')
                    ->limit(20)
                    ->get();

                foreach ($rows as $row) {
                    if (isset($mutualCandidates[(int) $row->id])) {
                        // Boost mutual candidate that also matches tags
                        $mutualCandidates[(int) $row->id]['score'] += (float) $row->matched_tags;
                        continue;
                    }

                    $tagCandidates[(int) $row->id] = [
                        'id'              => (int) $row->id,
                        'name'            => $row->name,
                        'username'        => $row->username,
                        'avatar_hash'     => $row->avatar_hash,
                        'followers_count' => (int) $row->followers_count,
                        'artworks_count'  => (int) $row->artworks_count,
                        'score'           => (float) $row->matched_tags * 2.0,
                        'reason'          => 'Matches your interests',
                    ];
                }
            }

            // ── 3. Merge & rank ───────────────────────────────────────────────
            $combined = array_values(array_merge($mutualCandidates, $tagCandidates));
            usort($combined, fn ($a, $b) => $b['score'] <=> $a['score']);
            $top = array_slice($combined, 0, self::LIMIT);

            if (count($top) < self::LIMIT) {
                $topIds = array_column($top, 'id');
                $excluded = array_unique(array_merge($followingIds, [$user->id], $topIds));
                $top = array_merge($top, $this->highQualityFallback($excluded, self::LIMIT - count($top)));
            }

            return array_map(fn (array $c): array => [
                'id'              => $c['id'],
                'name'            => $c['name'],
                'username'        => $c['username'],
                'url'             => $c['username'] ? '/@' . $c['username'] : '/profile/' . $c['id'],
                'avatar'          => AvatarUrl::forUser((int) $c['id'], $c['avatar_hash'] ?? null, 64),
                'followers_count' => (int) ($c['followers_count'] ?? 0),
                'artworks_count'  => (int) ($c['artworks_count']  ?? 0),
                'reason'          => $c['reason'] ?? null,
            ], $top);
        } catch (\Throwable $e) {
            Log::warning('SuggestedCreatorsController: failed', [
                'user_id' => $user->id,
                'error'   => $e->getMessage(),
            ]);

            return [];
        }
    }

    /**
     * @param array<int, int> $excludedIds
     * @return array<int, array<string, mixed>>
     */
    private function highQualityFallback(array $excludedIds, int $limit): array
    {
        if ($limit <= 0) {
            return [];
        }

        $rows = 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')
            ->whereNotIn('u.id', $excludedIds)
            ->where('u.is_active', true)
            ->selectRaw('
                u.id,
                u.name,
                u.username,
                up.avatar_hash,
                COALESCE(us.followers_count, 0) as followers_count,
                COALESCE(us.artworks_count, 0) as artworks_count
            ')
            ->orderByDesc('followers_count')
            ->limit($limit)
            ->get();

        return $rows->map(fn ($r) => [
            'id'              => (int) $r->id,
            'name'            => $r->name,
            'username'        => $r->username,
            'avatar_hash'     => $r->avatar_hash,
            'followers_count' => (int) $r->followers_count,
            'artworks_count'  => (int) $r->artworks_count,
            'score'           => (float) $r->followers_count * 0.1,
            'reason'          => 'Popular creator',
        ])->all();
    }
}
