<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Artwork;
use App\Models\Group;
use App\Models\GroupBadge;
use App\Models\GroupContributorStat;
use App\Models\GroupMember;
use App\Models\GroupMemberBadge;
use App\Models\GroupProject;
use App\Models\GroupRelease;
use App\Models\GroupReleaseContributor;
use App\Models\User;
use App\Support\AvatarUrl;
use Carbon\CarbonImmutable;
use Illuminate\Support\Collection;

class GroupReputationService
{
    public function __construct(
        private readonly NotificationService $notifications,
    ) {
    }

    public function refreshGroup(Group $group): void
    {
        $userIds = $this->contributorUserIds($group);

        GroupContributorStat::query()
            ->where('group_id', $group->id)
            ->whereNotIn('user_id', $userIds)
            ->delete();

        foreach ($userIds as $userId) {
            GroupContributorStat::query()->updateOrCreate(
                [
                    'group_id' => (int) $group->id,
                    'user_id' => $userId,
                ],
                $this->statPayload($group, $userId)
            );
        }

        $this->awardGroupBadges($group);
        $this->awardMemberBadges($group);
    }

    public function topContributors(Group $group, int $limit = 6): array
    {
        return GroupContributorStat::query()
            ->with(['user.profile'])
            ->where('group_id', $group->id)
            ->orderByDesc('release_count')
            ->orderByDesc('credited_artworks_count')
            ->orderByDesc('review_actions_count')
            ->limit(max(1, min(24, $limit)))
            ->get()
            ->map(fn (GroupContributorStat $stat): array => $this->mapContributorStat($group, $stat))
            ->values()
            ->all();
    }

    public function summary(Group $group): array
    {
        $stats = GroupContributorStat::query()->where('group_id', $group->id);

        return [
            'top_contributors' => $this->topContributors($group, 8),
            'counts' => [
                'contributors' => (clone $stats)->count(),
                'release_contributors' => (clone $stats)->where('release_count', '>', 0)->count(),
                'reliable_reviewers' => (clone $stats)->where('review_actions_count', '>=', 5)->count(),
                'trusted_contributors' => (clone $stats)->where('approved_submissions_count', '>=', 3)->count(),
                'group_badges' => (int) $group->badges()->count(),
                'member_badges' => (int) $group->memberBadges()->count(),
            ],
            'recent_badges' => $this->groupBadges($group, 8),
            'member_badge_unlocks' => $this->recentMemberBadges($group, 8),
        ];
    }

    public function trustSignals(Group $group): array
    {
        $releaseCount = isset($group->public_releases_count)
            ? (int) $group->public_releases_count
            : (int) $group->releases()->where('status', GroupRelease::STATUS_RELEASED)->count();

        $recentReleaseCount = isset($group->recent_public_releases_count)
            ? (int) $group->recent_public_releases_count
            : (int) $group->releases()
                ->where('status', GroupRelease::STATUS_RELEASED)
                ->where('released_at', '>=', now()->subDays(45))
                ->count();

        $activeMembers = (isset($group->active_members_count)
            ? (int) $group->active_members_count
            : ($group->relationLoaded('members')
                ? (int) $group->members->where('status', Group::STATUS_ACTIVE)->count()
                : (int) $group->members()->where('status', Group::STATUS_ACTIVE)->count())) + 1;

        $approvedArtworks = isset($group->approved_group_artworks_count)
            ? (int) $group->approved_group_artworks_count
            : (int) Artwork::query()
                ->where('group_id', $group->id)
                ->where('group_review_status', 'approved')
                ->count();

        $signals = [];

        if ($group->is_verified) {
            $signals[] = [
                'key' => 'verified',
                'label' => 'Verified',
                'tone' => 'sky',
                'reason' => 'This group has a verified or official identity on Nova.',
            ];
        }

        if ($group->last_activity_at && $group->last_activity_at->greaterThanOrEqualTo(now()->subDays(14))) {
            $signals[] = [
                'key' => 'active',
                'label' => 'Active',
                'tone' => 'emerald',
                'reason' => 'The group has posted or published work recently.',
            ];
        }

        if ($recentReleaseCount > 0) {
            $signals[] = [
                'key' => 'release_active',
                'label' => 'Release Active',
                'tone' => 'amber',
                'reason' => 'The group has published a release in the last 45 days.',
            ];
        }

        if ($releaseCount >= 2 && $approvedArtworks >= 6) {
            $signals[] = [
                'key' => 'trusted',
                'label' => 'Trusted',
                'tone' => 'sky',
                'reason' => 'Trust is earned through repeated releases and approved contributions.',
            ];
        }

        if ($activeMembers >= 4) {
            $signals[] = [
                'key' => 'collaborative',
                'label' => 'Collaborative',
                'tone' => 'violet',
                'reason' => 'Several active members are contributing to this group.',
            ];
        }

        if (($group->recruitmentProfile?->is_recruiting ?? false) === true) {
            $signals[] = [
                'key' => 'recruiting',
                'label' => 'Recruiting',
                'tone' => 'emerald',
                'reason' => 'The group is currently open to new collaborators.',
            ];
        }

        if ($signals === []) {
            $signals[] = [
                'key' => 'new_rising',
                'label' => 'New & Rising',
                'tone' => 'amber',
                'reason' => 'This group is still early, but active enough to remain discoverable.',
            ];
        }

        return $signals;
    }

    public function groupBadges(Group $group, int $limit = 6): array
    {
        $badges = $group->relationLoaded('badges')
            ? $group->badges->sortByDesc(fn (GroupBadge $badge) => $badge->awarded_at?->getTimestamp() ?? 0)
                ->take(max(1, min(24, $limit)))
            : $group->badges()
                ->latest('awarded_at')
                ->limit(max(1, min(24, $limit)))
                ->get();

        return $badges
            ->map(fn (GroupBadge $badge): array => [
                'key' => (string) $badge->badge_key,
                'label' => $this->badgeLabel('group', (string) $badge->badge_key),
                'awarded_at' => $badge->awarded_at?->toISOString(),
                'reason' => $badge->meta_json['reason'] ?? $this->badgeReason('group', (string) $badge->badge_key),
            ])
            ->values()
            ->all();
    }

    public function memberBadges(Group $group, User|int $user, int $limit = 4): array
    {
        $userId = $user instanceof User ? (int) $user->id : (int) $user;

        return GroupMemberBadge::query()
            ->where('group_id', $group->id)
            ->where('user_id', $userId)
            ->latest('awarded_at')
            ->limit(max(1, min(12, $limit)))
            ->get()
            ->map(fn (GroupMemberBadge $badge): array => [
                'key' => (string) $badge->badge_key,
                'label' => $this->badgeLabel('member', (string) $badge->badge_key),
                'awarded_at' => $badge->awarded_at?->toISOString(),
                'reason' => $badge->meta_json['reason'] ?? $this->badgeReason('member', (string) $badge->badge_key),
            ])
            ->values()
            ->all();
    }

    private function recentMemberBadges(Group $group, int $limit): array
    {
        return GroupMemberBadge::query()
            ->with('user.profile')
            ->where('group_id', $group->id)
            ->latest('awarded_at')
            ->limit(max(1, min(24, $limit)))
            ->get()
            ->map(fn (GroupMemberBadge $badge): array => [
                'user' => [
                    'id' => (int) $badge->user_id,
                    'name' => $badge->user?->name,
                    'username' => $badge->user?->username,
                    'avatar_url' => $badge->user ? AvatarUrl::forUser((int) $badge->user->id, $badge->user->profile?->avatar_hash, 72) : null,
                ],
                'badge' => [
                    'key' => (string) $badge->badge_key,
                    'label' => $this->badgeLabel('member', (string) $badge->badge_key),
                    'awarded_at' => $badge->awarded_at?->toISOString(),
                    'reason' => $badge->meta_json['reason'] ?? $this->badgeReason('member', (string) $badge->badge_key),
                ],
            ])
            ->values()
            ->all();
    }

    private function contributorUserIds(Group $group): array
    {
        return collect([(int) $group->owner_user_id])
            ->merge($group->members()->where('status', Group::STATUS_ACTIVE)->pluck('user_id'))
            ->merge($group->releases()->pluck('lead_user_id'))
            ->merge($group->releases()->pluck('created_by_user_id'))
            ->merge(GroupReleaseContributor::query()
                ->whereIn('group_release_id', $group->releases()->pluck('id'))
                ->pluck('user_id'))
            ->merge($group->projects()->pluck('lead_user_id'))
            ->merge($group->projects()->pluck('created_by_user_id'))
            ->merge($group->projects()->with('memberLinks')->get()->flatMap(fn (GroupProject $project) => $project->memberLinks->pluck('user_id')))
            ->merge(Artwork::query()->where('group_id', $group->id)->pluck('primary_author_user_id'))
            ->merge(Artwork::query()->where('group_id', $group->id)->pluck('uploaded_by_user_id'))
            ->merge(Artwork::query()->where('group_id', $group->id)->pluck('group_reviewed_by_user_id'))
            ->filter(fn ($id): bool => (int) $id > 0)
            ->map(fn ($id): int => (int) $id)
            ->unique()
            ->values()
            ->all();
    }

    private function statPayload(Group $group, int $userId): array
    {
        $creditedArtworksCount = Artwork::query()
            ->where('group_id', $group->id)
            ->where(function ($query) use ($userId): void {
                $query->where('primary_author_user_id', $userId)
                    ->orWhere('uploaded_by_user_id', $userId)
                    ->orWhereHas('contributors', fn ($contributorQuery) => $contributorQuery->where('user_id', $userId));
            })
            ->count();

        $releaseCount = GroupRelease::query()
            ->where('group_id', $group->id)
            ->where(function ($query) use ($userId): void {
                $query->where('lead_user_id', $userId)
                    ->orWhere('created_by_user_id', $userId)
                    ->orWhereHas('contributorLinks', fn ($contributorQuery) => $contributorQuery->where('user_id', $userId));
            })
            ->count();

        $projectCount = GroupProject::query()
            ->where('group_id', $group->id)
            ->where(function ($query) use ($userId): void {
                $query->where('lead_user_id', $userId)
                    ->orWhere('created_by_user_id', $userId)
                    ->orWhereHas('memberLinks', fn ($memberQuery) => $memberQuery->where('user_id', $userId));
            })
            ->count();

        $reviewActionsCount = Artwork::query()
            ->where('group_id', $group->id)
            ->where('group_reviewed_by_user_id', $userId)
            ->count();

        $approvedSubmissionsCount = Artwork::query()
            ->where('group_id', $group->id)
            ->where('uploaded_by_user_id', $userId)
            ->where('group_review_status', 'approved')
            ->count();

        return [
            'credited_artworks_count' => $creditedArtworksCount,
            'release_count' => $releaseCount,
            'project_count' => $projectCount,
            'review_actions_count' => $reviewActionsCount,
            'approved_submissions_count' => $approvedSubmissionsCount,
            'reputation_meta_json' => $this->reputationMeta($creditedArtworksCount, $releaseCount, $projectCount, $reviewActionsCount, $approvedSubmissionsCount),
        ];
    }

    private function reputationMeta(int $creditedArtworks, int $releaseCount, int $projectCount, int $reviewActions, int $approvedSubmissions): array
    {
        $creativeLevel = $this->levelLabel($creditedArtworks, [1 => 'Emerging', 5 => 'Established', 12 => 'Trusted']);
        $collaborationLevel = $this->levelLabel($projectCount + $releaseCount, [1 => 'Active', 4 => 'Reliable', 8 => 'Core']);
        $publishingLevel = $this->levelLabel($releaseCount + $approvedSubmissions, [1 => 'Contributing', 4 => 'Reliable', 8 => 'Trusted']);
        $leadershipLevel = $this->levelLabel($reviewActions, [1 => 'Reviewing', 5 => 'Reliable Reviewer', 12 => 'Leadership']);

        return [
            'trusted_indicator' => $approvedSubmissions >= 3 || $releaseCount >= 2 || $reviewActions >= 5,
            'summary' => trim(implode(' • ', array_filter([$creativeLevel, $collaborationLevel, $publishingLevel, $reviewActions > 0 ? $leadershipLevel : null]))),
            'dimensions' => [
                'creative_contribution' => [
                    'label' => $creativeLevel,
                    'value' => $creditedArtworks,
                    'reason' => 'Based on credited artworks and visible contributions in this group.',
                ],
                'collaboration_reliability' => [
                    'label' => $collaborationLevel,
                    'value' => $projectCount + $releaseCount,
                    'reason' => 'Based on projects, releases, and consistent participation.',
                ],
                'publishing_trust' => [
                    'label' => $publishingLevel,
                    'value' => $releaseCount + $approvedSubmissions,
                    'reason' => 'Based on published releases and approved submissions.',
                ],
                'review_leadership_trust' => [
                    'label' => $reviewActions > 0 ? $leadershipLevel : 'Not enough review activity yet',
                    'value' => $reviewActions,
                    'reason' => 'Based on review actions and approval responsibility inside the group.',
                ],
            ],
        ];
    }

    private function mapContributorStat(Group $group, GroupContributorStat $stat): array
    {
        $meta = $stat->reputation_meta_json ?? [];

        return [
            'user' => [
                'id' => (int) $stat->user_id,
                'name' => $stat->user?->name,
                'username' => $stat->user?->username,
                'avatar_url' => $stat->user ? AvatarUrl::forUser((int) $stat->user->id, $stat->user->profile?->avatar_hash, 72) : null,
                'profile_url' => $stat->user?->username ? route('profile.show', ['username' => strtolower((string) $stat->user->username)]) : null,
            ],
            'joined_at' => $this->memberJoinedAt($group, $stat->user_id),
            'counts' => [
                'credited_artworks' => (int) $stat->credited_artworks_count,
                'releases' => (int) $stat->release_count,
                'projects' => (int) $stat->project_count,
                'review_actions' => (int) $stat->review_actions_count,
                'approved_submissions' => (int) $stat->approved_submissions_count,
            ],
            'summary' => $meta['summary'] ?? null,
            'trusted_indicator' => (bool) ($meta['trusted_indicator'] ?? false),
            'dimensions' => $meta['dimensions'] ?? [],
            'badges' => $this->memberBadges($group, (int) $stat->user_id),
            'last_active_contribution_at' => $this->lastActiveContributionAt($group, (int) $stat->user_id),
        ];
    }

    private function awardGroupBadges(Group $group): void
    {
        $publicReleaseCount = (int) $group->releases()->where('status', GroupRelease::STATUS_RELEASED)->count();
        $publishedArtworksCount = (int) Artwork::query()->where('group_id', $group->id)->where('artwork_status', 'published')->count();
        $activeMembers = (int) $group->members()->where('status', Group::STATUS_ACTIVE)->count() + 1;
        $eventsCount = (int) $group->events()->where('status', 'published')->count();
        $challengeCount = (int) $group->challenges()->whereIn('status', ['published', 'active'])->count();

        $this->awardGroupBadge($group, 'first_release', $publicReleaseCount >= 1);
        $this->awardGroupBadge($group, 'ten_releases', $publicReleaseCount >= 10);
        $this->awardGroupBadge($group, 'hundred_published_artworks', $publishedArtworksCount >= 100);
        $this->awardGroupBadge($group, 'community_favorite', (int) $group->followers_count >= 25);
        $this->awardGroupBadge($group, 'consistent_activity', $group->last_activity_at?->greaterThanOrEqualTo(now()->subDays(30)) === true);
        $this->awardGroupBadge($group, 'event_host', $eventsCount >= 3);
        $this->awardGroupBadge($group, 'challenge_organizer', $challengeCount >= 2);
        $this->awardGroupBadge($group, 'collaborative_group', $activeMembers >= 4 && $publicReleaseCount >= 1);
        $this->awardGroupBadge($group, 'trusted_group', $publicReleaseCount >= 2 && $publishedArtworksCount >= 12);
    }

    private function awardMemberBadges(Group $group): void
    {
        $stats = GroupContributorStat::query()->where('group_id', $group->id)->get();
        $userIds = $stats->pluck('user_id')->map(static fn ($id): int => (int) $id)->unique()->values();

        $projectLeadIds = GroupProject::query()
            ->where('group_id', $group->id)
            ->whereIn('lead_user_id', $userIds)
            ->pluck('lead_user_id')
            ->map(static fn ($id): int => (int) $id)
            ->flip();

        $assetCounts = $group->assets()
            ->selectRaw('uploaded_by_user_id, COUNT(*) as aggregate')
            ->whereIn('uploaded_by_user_id', $userIds)
            ->groupBy('uploaded_by_user_id')
            ->pluck('aggregate', 'uploaded_by_user_id');

        $foundingMemberIds = GroupMember::query()
            ->where('group_id', $group->id)
            ->whereIn('user_id', $userIds)
            ->when($group->created_at, fn ($query) => $query->where('accepted_at', '<=', $group->created_at->copy()->addDays(30)))
            ->pluck('user_id')
            ->map(static fn ($id): int => (int) $id)
            ->flip();

        foreach ($stats as $stat) {
            $userId = (int) $stat->user_id;

            $this->awardMemberBadge($group, (int) $stat->user_id, 'first_group_contribution', (int) $stat->credited_artworks_count >= 1);
            $this->awardMemberBadge($group, (int) $stat->user_id, 'ten_group_contributions', (int) $stat->credited_artworks_count >= 10);
            $this->awardMemberBadge($group, (int) $stat->user_id, 'release_contributor', (int) $stat->release_count >= 1);
            $this->awardMemberBadge($group, $userId, 'project_lead', $projectLeadIds->has($userId));
            $this->awardMemberBadge($group, $userId, 'reliable_reviewer', (int) $stat->review_actions_count >= 5);
            $this->awardMemberBadge($group, $userId, 'long_term_collaborator', ((int) $stat->project_count + (int) $stat->release_count) >= 5);
            $this->awardMemberBadge($group, $userId, 'founding_member', (int) $group->owner_user_id === $userId || $foundingMemberIds->has($userId));
            $this->awardMemberBadge($group, $userId, 'asset_builder', (int) ($assetCounts[$userId] ?? 0) >= 3);
        }
    }

    private function awardGroupBadge(Group $group, string $badgeKey, bool $shouldAward): void
    {
        if (! $shouldAward) {
            return;
        }

        $badge = GroupBadge::query()->firstOrCreate(
            [
                'group_id' => (int) $group->id,
                'badge_key' => $badgeKey,
            ],
            [
                'awarded_at' => now(),
                'meta_json' => ['reason' => $this->badgeReason('group', $badgeKey)],
            ]
        );

        if ($badge->wasRecentlyCreated) {
            $badgeLabel = $this->badgeLabel('group', $badgeKey);
            $url = route('studio.groups.reputation', ['group' => $group]);

            foreach ($this->badgeManagerRecipients($group) as $recipient) {
                $this->notifications->notifyGroupBadgeEarned($recipient, $group, $badgeLabel, $url);
            }
        }
    }

    private function awardMemberBadge(Group $group, int $userId, string $badgeKey, bool $shouldAward): void
    {
        if (! $shouldAward) {
            return;
        }

        $badge = GroupMemberBadge::query()->firstOrCreate(
            [
                'group_id' => (int) $group->id,
                'user_id' => $userId,
                'badge_key' => $badgeKey,
            ],
            [
                'awarded_at' => now(),
                'meta_json' => ['reason' => $this->badgeReason('member', $badgeKey)],
            ]
        );

        if ($badge->wasRecentlyCreated) {
            $recipient = User::query()->find($userId);

            if ($recipient) {
                $this->notifications->notifyGroupMemberBadgeEarned(
                    $recipient,
                    $group,
                    $this->badgeLabel('member', $badgeKey),
                    route('groups.show', ['group' => $group])
                );
            }
        }
    }

    private function badgeManagerRecipients(Group $group): Collection
    {
        $owner = $group->relationLoaded('owner') ? $group->owner : $group->owner()->first();
        $admins = $group->members()
            ->with('user.profile')
            ->where('status', Group::STATUS_ACTIVE)
            ->where('role', Group::ROLE_ADMIN)
            ->get()
            ->pluck('user');

        return collect([$owner])
            ->merge($admins)
            ->filter(fn ($user): bool => $user instanceof User)
            ->unique(fn (User $user): int => (int) $user->id)
            ->values();
    }

    private function badgeLabel(string $scope, string $badgeKey): string
    {
        return (string) config(sprintf('groups.badges.%s.%s', $scope, $badgeKey), str_replace('_', ' ', $badgeKey));
    }

    private function badgeReason(string $scope, string $badgeKey): string
    {
        return match ($scope . ':' . $badgeKey) {
            'group:first_release' => 'Earned by publishing a first release.',
            'group:ten_releases' => 'Earned by publishing ten releases.',
            'group:hundred_published_artworks' => 'Earned by publishing one hundred group artworks.',
            'group:community_favorite' => 'Earned by sustained follower interest.',
            'group:consistent_activity' => 'Earned by staying active over recent weeks.',
            'group:event_host' => 'Earned by hosting multiple published events.',
            'group:challenge_organizer' => 'Earned by running multiple challenges.',
            'group:collaborative_group' => 'Earned by keeping several contributors active and releasing together.',
            'group:trusted_group' => 'Earned through repeated public releases and approved work.',
            'member:first_group_contribution' => 'Earned by making a first credited contribution to the group.',
            'member:ten_group_contributions' => 'Earned by making ten credited group contributions.',
            'member:release_contributor' => 'Earned by contributing to a group release.',
            'member:project_lead' => 'Earned by leading a group project.',
            'member:reliable_reviewer' => 'Earned through repeated group review actions.',
            'member:long_term_collaborator' => 'Earned through consistent long-term collaboration.',
            'member:founding_member' => 'Earned by helping the group from its early formation stage.',
            'member:asset_builder' => 'Earned by supplying multiple shared group assets.',
            default => 'Earned through visible group activity.',
        };
    }

    private function memberJoinedAt(Group $group, int $userId): ?string
    {
        if ((int) $group->owner_user_id === $userId) {
            return $group->created_at?->toISOString();
        }

        $member = GroupMember::query()
            ->where('group_id', $group->id)
            ->where('user_id', $userId)
            ->first();

        return $member?->accepted_at?->toISOString() ?? $member?->created_at?->toISOString();
    }

    private function lastActiveContributionAt(Group $group, int $userId): ?string
    {
        $timestamps = collect([
            Artwork::query()->where('group_id', $group->id)->where('uploaded_by_user_id', $userId)->max('updated_at'),
            GroupProject::query()->where('group_id', $group->id)->where('updated_at', '!=', null)->where(function ($query) use ($userId): void {
                $query->where('lead_user_id', $userId)
                    ->orWhere('created_by_user_id', $userId)
                    ->orWhereHas('memberLinks', fn ($memberQuery) => $memberQuery->where('user_id', $userId));
            })->max('updated_at'),
            GroupRelease::query()->where('group_id', $group->id)->where(function ($query) use ($userId): void {
                $query->where('lead_user_id', $userId)
                    ->orWhere('created_by_user_id', $userId)
                    ->orWhereHas('contributorLinks', fn ($contributorQuery) => $contributorQuery->where('user_id', $userId));
            })->max('updated_at'),
        ])->filter();

        $latest = $timestamps->sortDesc()->first();

        return $latest ? CarbonImmutable::parse((string) $latest)->toISOString() : null;
    }

    private function isFoundingMember(Group $group, int $userId): bool
    {
        if ((int) $group->owner_user_id === $userId) {
            return true;
        }

        $member = GroupMember::query()
            ->where('group_id', $group->id)
            ->where('user_id', $userId)
            ->first();

        if (! $member?->accepted_at || ! $group->created_at) {
            return false;
        }

        return $member->accepted_at->lessThanOrEqualTo($group->created_at->copy()->addDays(30));
    }

    private function levelLabel(int $value, array $thresholds): string
    {
        $label = 'New';

        foreach ($thresholds as $threshold => $candidate) {
            if ($value >= $threshold) {
                $label = $candidate;
            }
        }

        return $label;
    }
}