<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Artwork;
use App\Models\GroupChallenge;
use App\Models\Group;
use App\Models\GroupActivityItem;
use App\Models\GroupDiscoveryMetric;
use App\Models\GroupEvent;
use App\Models\GroupProject;
use App\Models\GroupRelease;
use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;

class GroupDiscoveryService
{
    public function __construct(
        private readonly GroupCardService $cards,
    ) {
    }

    public function refresh(Group $group): GroupDiscoveryMetric
    {
        $publicReleaseCount = (int) $group->releases()
            ->where('visibility', GroupRelease::VISIBILITY_PUBLIC)
            ->where('status', GroupRelease::STATUS_RELEASED)
            ->count();
        $recentReleaseCount = (int) $group->releases()
            ->where('visibility', GroupRelease::VISIBILITY_PUBLIC)
            ->where('status', GroupRelease::STATUS_RELEASED)
            ->where('released_at', '>=', now()->subDays(60))
            ->count();
        $recentPublicActivity = (int) GroupActivityItem::query()
            ->where('group_id', $group->id)
            ->where('visibility', GroupActivityItem::VISIBILITY_PUBLIC)
            ->where('occurred_at', '>=', now()->subDays(30))
            ->count();
        $publishedArtworks = (int) Artwork::query()
            ->where('group_id', $group->id)
            ->where('artwork_status', 'published')
            ->count();
        $activeMembers = (int) $group->members()->where('status', Group::STATUS_ACTIVE)->count() + 1;

        $freshnessScore = $this->freshnessScore($group);
        $activityScore = min(100, ($recentPublicActivity * 12) + ($publishedArtworks * 0.5));
        $releaseScore = min(100, ($publicReleaseCount * 14) + ($recentReleaseCount * 12));
        $collaborationScore = min(100, ($activeMembers * 10) + ($group->contributorStats()->count() * 4));
        $trustScore = $group->status === Group::LIFECYCLE_SUSPENDED
            ? 0
            : min(100, 25 + ($group->is_verified ? 20 : 0) + ($publicReleaseCount * 10) + ($publishedArtworks * 0.35) + ($group->followers_count * 0.2));

        return GroupDiscoveryMetric::query()->updateOrCreate(
            ['group_id' => (int) $group->id],
            [
                'freshness_score' => $freshnessScore,
                'activity_score' => round($activityScore, 2),
                'release_score' => round($releaseScore, 2),
                'collaboration_score' => round($collaborationScore, 2),
                'trust_score' => round($trustScore, 2),
                'last_calculated_at' => now(),
            ]
        );
    }

    public function publicListing(?User $viewer, string $surface = 'featured', int $page = 1, int $perPage = 24): LengthAwarePaginator
    {
        $groups = $this->publicGroupBaseQuery()->get();

        $sorted = $this->sortGroups($groups, $surface);
        $page = max(1, $page);
        $perPage = max(1, min($perPage, 48));
        $slice = $sorted->forPage($page, $perPage)->values();

        return new LengthAwarePaginator($slice, $sorted->count(), $perPage, $page, [
            'path' => request()->url(),
            'query' => request()->query(),
        ]);
    }

    public function spotlightCard(?User $viewer = null, string $surface = 'featured'): ?array
    {
        return $this->surfaceCards($viewer, $surface, 1)[0] ?? null;
    }

    public function surfaceCards(?User $viewer = null, string $surface = 'featured', int $limit = 6): array
    {
        return $this->sortGroups($this->publicGroupBaseQuery()->get(), $surface)
            ->take(max(1, $limit))
            ->map(fn (Group $group): array => $this->cards->mapGroupCard($group, $viewer))
            ->values()
            ->all();
    }

    public function searchCards(string $query, ?User $viewer = null, int $limit = 8): array
    {
        $normalized = mb_strtolower(trim($query));

        if (mb_strlen($normalized) < 2) {
            return [];
        }

        $groups = $this->publicGroupBaseQuery()
            ->where(function (Builder $builder) use ($normalized): void {
                $builder->whereRaw('LOWER(name) LIKE ?', ['%' . $normalized . '%'])
                    ->orWhereRaw('LOWER(slug) LIKE ?', ['%' . $normalized . '%'])
                    ->orWhereRaw('LOWER(headline) LIKE ?', ['%' . $normalized . '%'])
                    ->orWhereRaw('LOWER(bio) LIKE ?', ['%' . $normalized . '%'])
                    ->orWhereHas('recruitmentProfile', function (Builder $recruitmentQuery) use ($normalized): void {
                        $recruitmentQuery->whereRaw('LOWER(headline) LIKE ?', ['%' . $normalized . '%'])
                            ->orWhereRaw('LOWER(description) LIKE ?', ['%' . $normalized . '%'])
                            ->orWhereRaw('LOWER(roles_json) LIKE ?', ['%' . $normalized . '%'])
                            ->orWhereRaw('LOWER(skills_json) LIKE ?', ['%' . $normalized . '%']);
                    })
                    ->orWhereHas('releases', function (Builder $releaseQuery) use ($normalized): void {
                        $releaseQuery->where('visibility', GroupRelease::VISIBILITY_PUBLIC)
                            ->where('status', GroupRelease::STATUS_RELEASED)
                            ->where(function (Builder $nestedQuery) use ($normalized): void {
                                $nestedQuery->whereRaw('LOWER(title) LIKE ?', ['%' . $normalized . '%'])
                                    ->orWhereRaw('LOWER(summary) LIKE ?', ['%' . $normalized . '%'])
                                    ->orWhereRaw('LOWER(description) LIKE ?', ['%' . $normalized . '%'])
                                    ->orWhereRaw('LOWER(release_notes) LIKE ?', ['%' . $normalized . '%']);
                            });
                    })
                    ->orWhereHas('projects', function (Builder $projectQuery) use ($normalized): void {
                        $projectQuery->where('visibility', GroupProject::VISIBILITY_PUBLIC)
                            ->where(function (Builder $nestedQuery) use ($normalized): void {
                                $nestedQuery->whereRaw('LOWER(title) LIKE ?', ['%' . $normalized . '%'])
                                    ->orWhereRaw('LOWER(summary) LIKE ?', ['%' . $normalized . '%'])
                                    ->orWhereRaw('LOWER(description) LIKE ?', ['%' . $normalized . '%']);
                            });
                    })
                    ->orWhereHas('challenges', function (Builder $challengeQuery) use ($normalized): void {
                        $challengeQuery->where('visibility', GroupChallenge::VISIBILITY_PUBLIC)
                            ->whereIn('status', [GroupChallenge::STATUS_PUBLISHED, GroupChallenge::STATUS_ACTIVE])
                            ->where(function (Builder $nestedQuery) use ($normalized): void {
                                $nestedQuery->whereRaw('LOWER(title) LIKE ?', ['%' . $normalized . '%'])
                                    ->orWhereRaw('LOWER(summary) LIKE ?', ['%' . $normalized . '%'])
                                    ->orWhereRaw('LOWER(description) LIKE ?', ['%' . $normalized . '%']);
                            });
                    })
                    ->orWhereHas('events', function (Builder $eventQuery) use ($normalized): void {
                        $eventQuery->where('visibility', GroupEvent::VISIBILITY_PUBLIC)
                            ->where('status', GroupEvent::STATUS_PUBLISHED)
                            ->where(function (Builder $nestedQuery) use ($normalized): void {
                                $nestedQuery->whereRaw('LOWER(title) LIKE ?', ['%' . $normalized . '%'])
                                    ->orWhereRaw('LOWER(summary) LIKE ?', ['%' . $normalized . '%'])
                                    ->orWhereRaw('LOWER(description) LIKE ?', ['%' . $normalized . '%']);
                            });
                    })
                    ->orWhereHas('badges', function (Builder $badgeQuery) use ($normalized): void {
                        $badgeQuery->whereRaw('LOWER(badge_key) LIKE ?', ['%' . $normalized . '%'])
                            ->orWhereRaw("LOWER(REPLACE(badge_key, '_', ' ')) LIKE ?", ['%' . $normalized . '%']);
                    })
                    ->orWhereHas('members.user', function (Builder $userQuery) use ($normalized): void {
                        $userQuery->whereRaw('LOWER(name) LIKE ?', ['%' . $normalized . '%'])
                            ->orWhereRaw('LOWER(username) LIKE ?', ['%' . $normalized . '%']);
                    });
            })
            ->limit(max($limit * 3, 12))
            ->get();

        return $groups
            ->sortByDesc(fn (Group $group): float => $this->searchWeight($group, $normalized))
            ->take(max(1, $limit))
            ->map(fn (Group $group): array => $this->cards->mapGroupCard($group, $viewer))
            ->values()
            ->all();
    }

    public function publicGroupCount(): int
    {
        return Group::query()->public()->count();
    }

    public function availableSurfaces(): array
    {
        return [
            ['value' => 'featured', 'label' => 'Featured'],
            ['value' => 'recruiting', 'label' => 'Recruiting'],
            ['value' => 'new_rising', 'label' => 'New & Rising'],
            ['value' => 'trusted', 'label' => 'Trusted'],
            ['value' => 'recent_releases', 'label' => 'Recent releases'],
            ['value' => 'featured_projects', 'label' => 'Featured projects'],
            ['value' => 'current_challenges', 'label' => 'Current challenges'],
            ['value' => 'upcoming_events', 'label' => 'Upcoming events'],
        ];
    }

    private function publicGroupBaseQuery(): Builder
    {
        return Group::query()
            ->with(['owner.profile', 'recruitmentProfile', 'discoveryMetric', 'members', 'badges'])
            ->withCount([
                'members as active_members_count' => fn (Builder $query) => $query->where('status', Group::STATUS_ACTIVE),
                'releases as public_releases_count' => fn (Builder $query) => $query
                    ->where('visibility', GroupRelease::VISIBILITY_PUBLIC)
                    ->where('status', GroupRelease::STATUS_RELEASED),
                'releases as recent_public_releases_count' => fn (Builder $query) => $query
                    ->where('visibility', GroupRelease::VISIBILITY_PUBLIC)
                    ->where('status', GroupRelease::STATUS_RELEASED)
                    ->where('released_at', '>=', now()->subDays(60)),
                'projects as public_projects_count' => fn (Builder $query) => $query
                    ->where('visibility', GroupProject::VISIBILITY_PUBLIC)
                    ->whereIn('status', [GroupProject::STATUS_ACTIVE, GroupProject::STATUS_REVIEW, GroupProject::STATUS_RELEASED]),
                'challenges as active_public_challenges_count' => fn (Builder $query) => $query
                    ->where('visibility', GroupChallenge::VISIBILITY_PUBLIC)
                    ->whereIn('status', [GroupChallenge::STATUS_PUBLISHED, GroupChallenge::STATUS_ACTIVE]),
                'events as upcoming_public_events_count' => fn (Builder $query) => $query
                    ->where('visibility', GroupEvent::VISIBILITY_PUBLIC)
                    ->where('status', GroupEvent::STATUS_PUBLISHED)
                    ->where('start_at', '>=', now()),
                'activityItems as public_activity_30d_count' => fn (Builder $query) => $query
                    ->where('visibility', GroupActivityItem::VISIBILITY_PUBLIC)
                    ->where('occurred_at', '>=', now()->subDays(30)),
                'contributorStats as contributor_stats_count',
            ])
            ->withMax([
                'releases as latest_public_release_at' => fn (Builder $query) => $query
                    ->where('visibility', GroupRelease::VISIBILITY_PUBLIC)
                    ->where('status', GroupRelease::STATUS_RELEASED),
            ], 'released_at')
            ->public();
    }

    private function sortGroups(Collection $groups, string $surface): Collection
    {
        return (match ($surface) {
            'recent_releases' => $groups->sortByDesc(fn (Group $group): string => (string) ($group->latest_public_release_at ?? '')),
            'featured_projects' => $groups->sortByDesc(fn (Group $group): float => ((int) ($group->public_projects_count ?? 0) > 0 ? 1000 : 0) + $this->discoveryWeight($group, 'collaboration') + $this->discoveryWeight($group, 'activity')),
            'current_challenges' => $groups->sortByDesc(fn (Group $group): float => ((int) ($group->active_public_challenges_count ?? 0) > 0 ? 1000 : 0) + $this->discoveryWeight($group, 'freshness') + $this->discoveryWeight($group, 'activity')),
            'upcoming_events' => $groups->sortByDesc(fn (Group $group): float => ((int) ($group->upcoming_public_events_count ?? 0) > 0 ? 1000 : 0) + $this->discoveryWeight($group, 'activity') + $this->discoveryWeight($group, 'trust')),
            'recruiting' => $groups->sortByDesc(fn (Group $group): float => (($group->recruitmentProfile?->is_recruiting ?? false) ? 1000 : 0) + $this->discoveryWeight($group, 'activity') + ($group->followers_count * 0.03)),
            'new_rising' => $groups->sortByDesc(fn (Group $group): float => ($this->freshnessScore($group) * 1.2) + min(20, max(0, 50 - ((int) $group->followers_count / 2)))),
            'trusted' => $groups->sortByDesc(fn (Group $group): float => $this->discoveryWeight($group, 'trust') + $this->discoveryWeight($group, 'release')),
            default => $groups->sortByDesc(fn (Group $group): float => $this->discoveryWeight($group, 'trust') + $this->discoveryWeight($group, 'activity') + $this->discoveryWeight($group, 'collaboration')),
        })->values();
    }

    private function searchWeight(Group $group, string $query): float
    {
        $name = mb_strtolower((string) $group->name);
        $slug = mb_strtolower((string) $group->slug);
        $headline = mb_strtolower((string) ($group->headline ?? ''));
        $bio = mb_strtolower((string) ($group->bio ?? ''));

        $exact = $name === $query || $slug === $query ? 1800 : 0;
        $prefix = str_starts_with($name, $query) || str_starts_with($slug, $query) ? 600 : 0;
        $contains = str_contains($name, $query) || str_contains($slug, $query) ? 240 : 0;
        $descriptive = str_contains($headline, $query) || str_contains($bio, $query) ? 90 : 0;

        return $exact
            + $prefix
            + $contains
            + $descriptive
            + ($this->discoveryWeight($group, 'trust') * 1.25)
            + $this->discoveryWeight($group, 'activity')
            + ($this->discoveryWeight($group, 'release') * 0.8)
            + ((float) ($group->followers_count ?? 0) * 0.08)
            + (($group->recruitmentProfile?->is_recruiting ?? false) ? 15 : 0);
    }

    private function discoveryWeight(Group $group, string $dimension): float
    {
        $metric = $group->relationLoaded('discoveryMetric') ? $group->discoveryMetric : $group->discoveryMetric()->first();

        if (! $metric) {
            $metric = $this->refresh($group);
        }

        return match ($dimension) {
            'activity' => (float) $metric->activity_score,
            'release' => (float) $metric->release_score,
            'collaboration' => (float) $metric->collaboration_score,
            'freshness' => (float) $metric->freshness_score,
            default => (float) $metric->trust_score,
        };
    }

    private function freshnessScore(Group $group): float
    {
        if (! $group->last_activity_at) {
            return 20.0;
        }

        $days = $group->last_activity_at->diffInDays(now());

        return match (true) {
            $days <= 7 => 100.0,
            $days <= 14 => 80.0,
            $days <= 30 => 60.0,
            $days <= 60 => 40.0,
            default => 20.0,
        };
    }
}