<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\User;
use App\Services\Recommendation\UserPreferenceBuilder;
use App\Support\AvatarUrl;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

final class UserSuggestionService
{
    public function __construct(
        private readonly UserPreferenceBuilder $preferenceBuilder,
        private readonly FollowService $followService,
    ) {
    }

    public function suggestFor(User $viewer, int $limit = 8): array
    {
        $resolvedLimit = max(1, min(24, $limit));
        $cacheKey = sprintf('user_suggestions:v2:%d:%d', (int) $viewer->id, $resolvedLimit);

        return Cache::remember($cacheKey, now()->addMinutes(15), function () use ($viewer, $resolvedLimit): array {
            try {
                return $this->buildSuggestions($viewer, $resolvedLimit);
            } catch (\Throwable $e) {
                Log::warning('UserSuggestionService failed', [
                    'viewer_id' => (int) $viewer->id,
                    'error' => $e->getMessage(),
                ]);

                return [];
            }
        });
    }

    private function buildSuggestions(User $viewer, int $limit): array
    {
        $profile = $this->preferenceBuilder->build($viewer);
        $followingIds = DB::table('user_followers')
            ->where('follower_id', $viewer->id)
            ->pluck('user_id')
            ->map(fn ($id) => (int) $id)
            ->values()
            ->all();

        $excludedIds = array_values(array_unique(array_merge($followingIds, [(int) $viewer->id])));
        $topTagSlugs = array_slice($profile->topTagSlugs ?? [], 0, 10);
        $topCategoryIds = $this->topCategoryIdsForViewer((int) $viewer->id);

        $candidates = [];

        foreach ($this->mutualFollowCandidates($viewer, $followingIds) as $candidate) {
            $candidates[$candidate['id']] = $candidate;
        }

        foreach ($this->sharedInterestCandidates($viewer, $topTagSlugs, $topCategoryIds) as $candidate) {
            if (isset($candidates[$candidate['id']])) {
                $candidates[$candidate['id']]['score'] += $candidate['score'];
                $candidates[$candidate['id']]['reason'] = $candidates[$candidate['id']]['reason'] . ' · ' . $candidate['reason'];
                continue;
            }

            $candidates[$candidate['id']] = $candidate;
        }

        foreach ($this->trendingCreatorCandidates($excludedIds) as $candidate) {
            if (! isset($candidates[$candidate['id']])) {
                $candidates[$candidate['id']] = $candidate;
            }
        }

        foreach ($this->newActiveCreatorCandidates($excludedIds) as $candidate) {
            if (! isset($candidates[$candidate['id']])) {
                $candidates[$candidate['id']] = $candidate;
            }
        }

        $ranked = array_values(array_filter(
            $candidates,
            fn (array $candidate): bool => ! in_array((int) $candidate['id'], $excludedIds, true)
        ));

        usort($ranked, fn (array $left, array $right): int => $right['score'] <=> $left['score']);

        return array_map(function (array $candidate) use ($viewer): array {
            $context = $this->followService->relationshipContext((int) $viewer->id, (int) $candidate['id']);

            return [
                'id' => (int) $candidate['id'],
                'username' => (string) $candidate['username'],
                'name' => (string) ($candidate['name'] ?? $candidate['username']),
                'profile_url' => '/@' . strtolower((string) $candidate['username']),
                'avatar_url' => AvatarUrl::forUser((int) $candidate['id'], $candidate['avatar_hash'] ?? null, 64),
                'followers_count' => (int) ($candidate['followers_count'] ?? 0),
                'following_count' => (int) ($candidate['following_count'] ?? 0),
                'reason' => (string) ($candidate['reason'] ?? 'Recommended creator'),
                'context' => $context,
            ];
        }, array_slice($ranked, 0, $limit));
    }

    private function mutualFollowCandidates(User $viewer, array $followingIds): array
    {
        if ($followingIds === []) {
            return [];
        }

        return 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', '!=', $viewer->id)
            ->where('u.is_active', true)
            ->whereNull('u.deleted_at')
            ->selectRaw('u.id, u.username, u.name, up.avatar_hash, COALESCE(us.followers_count, 0) as followers_count, COALESCE(us.following_count, 0) as following_count, COUNT(*) as overlap_count')
            ->groupBy('u.id', 'u.username', 'u.name', 'up.avatar_hash', 'us.followers_count', 'us.following_count')
            ->orderByDesc('overlap_count')
            ->limit(20)
            ->get()
            ->map(fn ($row) => [
                'id' => (int) $row->id,
                'username' => $row->username,
                'name' => $row->name,
                'avatar_hash' => $row->avatar_hash,
                'followers_count' => (int) $row->followers_count,
                'following_count' => (int) $row->following_count,
                'score' => (float) $row->overlap_count * 3.0,
                'reason' => 'Popular in your network',
            ])
            ->all();
    }

    private function sharedInterestCandidates(User $viewer, array $topTagSlugs, array $topCategoryIds): array
    {
        if ($topTagSlugs === [] && $topCategoryIds === []) {
            return [];
        }

        $query = DB::table('users as u')
            ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
            ->leftJoin('user_statistics as us', 'us.user_id', '=', 'u.id')
            ->join('artworks as a', 'a.user_id', '=', 'u.id')
            ->leftJoin('artwork_tag as at', 'at.artwork_id', '=', 'a.id')
            ->leftJoin('tags as t', 't.id', '=', 'at.tag_id')
            ->leftJoin('artwork_category as ac', 'ac.artwork_id', '=', 'a.id')
            ->where('u.id', '!=', $viewer->id)
            ->where('u.is_active', true)
            ->whereNull('u.deleted_at')
            ->where('a.is_public', true)
            ->where('a.is_approved', true)
            ->whereNull('a.deleted_at')
            ->whereNotNull('a.published_at');

        $query->where(function ($builder) use ($topTagSlugs, $topCategoryIds): void {
            if ($topTagSlugs !== []) {
                $builder->orWhereIn('t.slug', $topTagSlugs);
            }

            if ($topCategoryIds !== []) {
                $builder->orWhereIn('ac.category_id', $topCategoryIds);
            }
        });

        return $query
            ->selectRaw('u.id, u.username, u.name, up.avatar_hash, COALESCE(us.followers_count, 0) as followers_count, COALESCE(us.following_count, 0) as following_count, COUNT(DISTINCT t.id) as matched_tags, COUNT(DISTINCT ac.category_id) as matched_categories')
            ->groupBy('u.id', 'u.username', 'u.name', 'up.avatar_hash', 'us.followers_count', 'us.following_count')
            ->orderByDesc(DB::raw('COUNT(DISTINCT t.id) + COUNT(DISTINCT ac.category_id)'))
            ->limit(20)
            ->get()
            ->map(fn ($row) => [
                'id' => (int) $row->id,
                'username' => $row->username,
                'name' => $row->name,
                'avatar_hash' => $row->avatar_hash,
                'followers_count' => (int) $row->followers_count,
                'following_count' => (int) $row->following_count,
                'score' => ((float) $row->matched_tags * 2.0) + (float) $row->matched_categories,
                'reason' => 'Shared tags and categories',
            ])
            ->all();
    }

    private function trendingCreatorCandidates(array $excludedIds): array
    {
        return DB::table('users as u')
            ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
            ->leftJoin('user_statistics as us', 'us.user_id', '=', 'u.id')
            ->join('artworks as a', 'a.user_id', '=', 'u.id')
            ->whereNotIn('u.id', $excludedIds)
            ->where('u.is_active', true)
            ->whereNull('u.deleted_at')
            ->where('a.is_public', true)
            ->where('a.is_approved', true)
            ->whereNull('a.deleted_at')
            ->where('a.published_at', '>=', now()->subDays(30))
            ->selectRaw('u.id, u.username, u.name, up.avatar_hash, COALESCE(us.followers_count, 0) as followers_count, COALESCE(us.following_count, 0) as following_count, COUNT(a.id) as recent_artworks')
            ->groupBy('u.id', 'u.username', 'u.name', 'up.avatar_hash', 'us.followers_count', 'us.following_count')
            ->orderByDesc('followers_count')
            ->orderByDesc('recent_artworks')
            ->limit(10)
            ->get()
            ->map(fn ($row) => [
                'id' => (int) $row->id,
                'username' => $row->username,
                'name' => $row->name,
                'avatar_hash' => $row->avatar_hash,
                'followers_count' => (int) $row->followers_count,
                'following_count' => (int) $row->following_count,
                'score' => ((float) $row->followers_count * 0.1) + (float) $row->recent_artworks,
                'reason' => 'Trending creator',
            ])
            ->all();
    }

    private function newActiveCreatorCandidates(array $excludedIds): array
    {
        return DB::table('users as u')
            ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
            ->leftJoin('user_statistics as us', 'us.user_id', '=', 'u.id')
            ->join('artworks as a', 'a.user_id', '=', 'u.id')
            ->whereNotIn('u.id', $excludedIds)
            ->where('u.is_active', true)
            ->whereNull('u.deleted_at')
            ->where('u.created_at', '>=', now()->subDays(60))
            ->where('a.is_public', true)
            ->where('a.is_approved', true)
            ->whereNull('a.deleted_at')
            ->where('a.published_at', '>=', now()->subDays(14))
            ->selectRaw('u.id, u.username, u.name, up.avatar_hash, COALESCE(us.followers_count, 0) as followers_count, COALESCE(us.following_count, 0) as following_count, COUNT(a.id) as recent_artworks')
            ->groupBy('u.id', 'u.username', 'u.name', 'up.avatar_hash', 'us.followers_count', 'us.following_count')
            ->orderByDesc('recent_artworks')
            ->orderByDesc('followers_count')
            ->limit(10)
            ->get()
            ->map(fn ($row) => [
                'id' => (int) $row->id,
                'username' => $row->username,
                'name' => $row->name,
                'avatar_hash' => $row->avatar_hash,
                'followers_count' => (int) $row->followers_count,
                'following_count' => (int) $row->following_count,
                'score' => ((float) $row->recent_artworks * 2.0) + ((float) $row->followers_count * 0.05),
                'reason' => 'New active creator',
            ])
            ->all();
    }

    private function topCategoryIdsForViewer(int $viewerId): array
    {
        return DB::table('artwork_category as ac')
            ->join('artworks as a', 'a.id', '=', 'ac.artwork_id')
            ->leftJoin('artwork_favourites as af', 'af.artwork_id', '=', 'a.id')
            ->where(function ($query) use ($viewerId): void {
                $query
                    ->where('a.user_id', $viewerId)
                    ->orWhere('af.user_id', $viewerId);
            })
            ->selectRaw('ac.category_id, COUNT(*) as weight')
            ->groupBy('ac.category_id')
            ->orderByDesc('weight')
            ->limit(6)
            ->pluck('category_id')
            ->map(fn ($id) => (int) $id)
            ->values()
            ->all();
    }
}
